diff --git a/.editorconfig b/.editorconfig index 49de0d370..66212dc13 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,11 +5,11 @@ # All files [*] indent_style = space +charset = utf-8 # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 insert_final_newline = true -charset = utf-8-bom ############################### # .NET Coding Conventions # ############################### diff --git a/.github/ISSUE_TEMPLATE/02b-net9-question-or-issue.yml b/.github/ISSUE_TEMPLATE/02b-net10-question-or-issue.yml similarity index 60% rename from .github/ISSUE_TEMPLATE/02b-net9-question-or-issue.yml rename to .github/ISSUE_TEMPLATE/02b-net10-question-or-issue.yml index 139a111be..a2b7b595b 100644 --- a/.github/ISSUE_TEMPLATE/02b-net9-question-or-issue.yml +++ b/.github/ISSUE_TEMPLATE/02b-net10-question-or-issue.yml @@ -1,11 +1,11 @@ -name: .NET 9 issue or question -description: Ask questions or raise issues related to .NET 9 -labels: [".NET 9"] +name: .NET 10 issue or question +description: Ask questions or raise issues related to .NET 10 +labels: [".NET 10"] body: - type: markdown attributes: value: | - Please use this template for any issues you have using .NET 9 or for any questions not answered in our [tracking thread for .NET 9 GA support](https://github.com/Azure/azure-functions-dotnet-worker/issues/2817). + Please use this template for any issues you have using .NET 10 or for any questions not answered in our [tracking thread for .NET 10 support](https://github.com/Azure/azure-functions-dotnet-worker/issues/3152). - id: description type: textarea attributes: diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..a1ef8b6a8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "dotnet-sdk" + directory: "/" + schedule: + interval: "weekly" + day: "wednesday" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-major" + - "version-update:semver-minor" diff --git a/.reporoot b/.reporoot new file mode 100644 index 000000000..96003cbd2 --- /dev/null +++ b/.reporoot @@ -0,0 +1 @@ +File to mark repo root. Do not edit. \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 34cd20ef5..7da35374a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,10 +1,5 @@ - - - true - - $(MSBuildThisFileDirectory) $(RepoRoot)eng/ diff --git a/DotNetWorker.Extensions.sln b/DotNetWorker.Extensions.sln new file mode 100644 index 000000000..940dc4111 --- /dev/null +++ b/DotNetWorker.Extensions.sln @@ -0,0 +1,252 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FD7243E4-BF18-43F8-8744-BA1D17ACF378}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{1B6B4B62-686E-44B4-9CEC-710DA34BC09E}" + ProjectSection(SolutionItems) = preProject + build\Common.props = build\Common.props + build\Extensions.props = build\Extensions.props + build\icon.png = build\icon.png + build\Icon.props = build\Icon.props + build\PackageInfo.props = build\PackageInfo.props + build\SharedReferences.targets = build\SharedReferences.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{42CB3DE7-B070-4BA1-8A7A-ACB237BEAA4E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtility", "test\TestUtility\TestUtility.csproj", "{C30B77A7-4085-422E-AADE-A4322989F5F8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{A7B4FF1E-3DF7-4F28-9333-D0961CDDF702}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http", "extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj", "{1E601406-6923-4CB9-AAD7-928E08906B10}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Abstractions", "extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj", "{B5F66802-0968-4B99-8E97-E42C744CE5CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage", "extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj", "{D6EAB0C1-491C-4723-B1F3-B6F5461CD359}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Timer", "extensions\Worker.Extensions.Timer\src\Worker.Extensions.Timer.csproj", "{4DBEC557-E5CC-41E9-9319-BC02615CF228}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.CosmosDB", "extensions\Worker.Extensions.CosmosDB\src\Worker.Extensions.CosmosDB.csproj", "{37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.EventGrid", "extensions\Worker.Extensions.EventGrid\src\Worker.Extensions.EventGrid.csproj", "{9C305047-0B81-4DA1-98D4-BC8B391A2297}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.ServiceBus", "extensions\Worker.Extensions.ServiceBus\src\Worker.Extensions.ServiceBus.csproj", "{660B4D9D-081A-4DCA-BD7A-E196F4529BFD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.EventHubs", "extensions\Worker.Extensions.EventHubs\src\Worker.Extensions.EventHubs.csproj", "{4A5770BE-6BB7-448F-A818-DD1450F8CBDC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.RabbitMQ", "extensions\Worker.Extensions.RabbitMQ\src\Worker.Extensions.RabbitMQ.csproj", "{55652CF7-E58B-4564-A76B-D49DC71878DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.SignalRService", "extensions\Worker.Extensions.SignalRService\src\Worker.Extensions.SignalRService.csproj", "{A9802FBA-CF59-462B-88A0-1D7D0D19C748}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.SendGrid", "extensions\Worker.Extensions.SendGrid\src\Worker.Extensions.SendGrid.csproj", "{59EB5B01-4756-48BB-8B37-70D29E3BDFB6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Warmup", "extensions\Worker.Extensions.Warmup\src\Worker.Extensions.Warmup.csproj", "{BDA0BCB4-BD26-4B10-B865-87082F290B38}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Kafka", "extensions\Worker.Extensions.Kafka\src\Worker.Extensions.Kafka.csproj", "{F63A63A9-FE09-4225-A293-6A58F9C1E520}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Worker", "Worker", "{0F6B5528-642F-4C46-AB4E-15E9B6CE24F9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{0EA6A975-2934-4837-9932-2328EFE23BFD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2EApp", "test\E2ETests\E2EApps\E2EApp\E2EApp.csproj", "{34CB5A87-0433-4C19-9CB3-E2F58119CDE9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETests", "test\E2ETests\E2ETests\E2ETests.csproj", "{059C8F76-20F9-42BD-A343-64EE3ACF1AF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Queues", "extensions\Worker.Extensions.Storage.Queues\src\Worker.Extensions.Storage.Queues.csproj", "{1D2A2B02-BFA8-4E53-9844-88359C5B2671}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Blobs", "extensions\Worker.Extensions.Storage.Blobs\src\Worker.Extensions.Storage.Blobs.csproj", "{FC352905-BD72-4049-8D32-3CBB9304FDC8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Tables", "extensions\Worker.Extensions.Tables\src\Worker.Extensions.Tables.csproj", "{004DEF24-7EBB-499D-BD1C-E940AC4E122D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Shared", "extensions\Worker.Extensions.Shared\Worker.Extensions.Shared.csproj", "{277D77B9-8915-41E3-8763-0B66328ADDDA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Rpc", "extensions\Worker.Extensions.Rpc\src\Worker.Extensions.Rpc.csproj", "{93B64F12-EBDD-46CE-B4FB-0904701F0032}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{AA4D318D-101B-49E7-A4EC-B34165AEDB33}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Rpc.Tests", "test\Worker.Extensions.Rpc.Tests\Worker.Extensions.Rpc.Tests.csproj", "{B13C9E5F-0E4B-413E-90AE-20B84B100364}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http.AspNetCore", "extensions\Worker.Extensions.Http.AspNetCore\src\Worker.Extensions.Http.AspNetCore.csproj", "{1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.SignalRService.Tests", "test\Worker.Extensions.SignalRService.Tests\Worker.Extensions.SignalRService.Tests.csproj", "{286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Tests", "test\Worker.Extensions.Tests\Worker.Extensions.Tests.csproj", "{17BDCE12-6964-4B87-B2AC-68CE270A3E9A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http.AspNetCore.Tests", "test\extensions\Worker.Extensions.Http.AspNetCore.Tests\Worker.Extensions.Http.AspNetCore.Tests.csproj", "{D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Shared.Tests", "test\Worker.Extensions.Shared.Tests\Worker.Extensions.Shared.Tests.csproj", "{B6342174-5436-4846-B43C-39D8E34AE5CF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Timer.Tests", "test\extensions\Worker.Extensions.Timer.Tests\Worker.Extensions.Timer.Tests.csproj", "{6947034E-C97F-4F78-940F-B6A398E23C9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.Extensions.Http.AspNetCore.Analyzers", "extensions\Worker.Extensions.Http.AspNetCore.Analyzers\src\Worker.Extensions.Http.AspNetCore.Analyzers.csproj", "{914B3E60-DE19-4827-956F-22080C817820}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C30B77A7-4085-422E-AADE-A4322989F5F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C30B77A7-4085-422E-AADE-A4322989F5F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C30B77A7-4085-422E-AADE-A4322989F5F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C30B77A7-4085-422E-AADE-A4322989F5F8}.Release|Any CPU.Build.0 = Release|Any CPU + {1E601406-6923-4CB9-AAD7-928E08906B10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E601406-6923-4CB9-AAD7-928E08906B10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E601406-6923-4CB9-AAD7-928E08906B10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E601406-6923-4CB9-AAD7-928E08906B10}.Release|Any CPU.Build.0 = Release|Any CPU + {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Release|Any CPU.Build.0 = Release|Any CPU + {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Release|Any CPU.Build.0 = Release|Any CPU + {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Release|Any CPU.Build.0 = Release|Any CPU + {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Release|Any CPU.Build.0 = Release|Any CPU + {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Release|Any CPU.Build.0 = Release|Any CPU + {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Release|Any CPU.Build.0 = Release|Any CPU + {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Release|Any CPU.Build.0 = Release|Any CPU + {55652CF7-E58B-4564-A76B-D49DC71878DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55652CF7-E58B-4564-A76B-D49DC71878DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55652CF7-E58B-4564-A76B-D49DC71878DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55652CF7-E58B-4564-A76B-D49DC71878DC}.Release|Any CPU.Build.0 = Release|Any CPU + {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Release|Any CPU.Build.0 = Release|Any CPU + {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Release|Any CPU.Build.0 = Release|Any CPU + {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Release|Any CPU.Build.0 = Release|Any CPU + {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Release|Any CPU.Build.0 = Release|Any CPU + {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Release|Any CPU.Build.0 = Release|Any CPU + {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Release|Any CPU.Build.0 = Release|Any CPU + {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Release|Any CPU.Build.0 = Release|Any CPU + {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Release|Any CPU.Build.0 = Release|Any CPU + {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Release|Any CPU.Build.0 = Release|Any CPU + {277D77B9-8915-41E3-8763-0B66328ADDDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {277D77B9-8915-41E3-8763-0B66328ADDDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {277D77B9-8915-41E3-8763-0B66328ADDDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {277D77B9-8915-41E3-8763-0B66328ADDDA}.Release|Any CPU.Build.0 = Release|Any CPU + {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Release|Any CPU.Build.0 = Release|Any CPU + {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Release|Any CPU.Build.0 = Release|Any CPU + {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Release|Any CPU.Build.0 = Release|Any CPU + {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Release|Any CPU.Build.0 = Release|Any CPU + {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Release|Any CPU.Build.0 = Release|Any CPU + {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Release|Any CPU.Build.0 = Release|Any CPU + {B6342174-5436-4846-B43C-39D8E34AE5CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6342174-5436-4846-B43C-39D8E34AE5CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6342174-5436-4846-B43C-39D8E34AE5CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6342174-5436-4846-B43C-39D8E34AE5CF}.Release|Any CPU.Build.0 = Release|Any CPU + {6947034E-C97F-4F78-940F-B6A398E23C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6947034E-C97F-4F78-940F-B6A398E23C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6947034E-C97F-4F78-940F-B6A398E23C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6947034E-C97F-4F78-940F-B6A398E23C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {914B3E60-DE19-4827-956F-22080C817820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {914B3E60-DE19-4827-956F-22080C817820}.Debug|Any CPU.Build.0 = Debug|Any CPU + {914B3E60-DE19-4827-956F-22080C817820}.Release|Any CPU.ActiveCfg = Release|Any CPU + {914B3E60-DE19-4827-956F-22080C817820}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C30B77A7-4085-422E-AADE-A4322989F5F8} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {1E601406-6923-4CB9-AAD7-928E08906B10} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {B5F66802-0968-4B99-8E97-E42C744CE5CE} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {D6EAB0C1-491C-4723-B1F3-B6F5461CD359} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {4DBEC557-E5CC-41E9-9319-BC02615CF228} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {9C305047-0B81-4DA1-98D4-BC8B391A2297} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {660B4D9D-081A-4DCA-BD7A-E196F4529BFD} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {4A5770BE-6BB7-448F-A818-DD1450F8CBDC} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {55652CF7-E58B-4564-A76B-D49DC71878DC} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {A9802FBA-CF59-462B-88A0-1D7D0D19C748} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {59EB5B01-4756-48BB-8B37-70D29E3BDFB6} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {BDA0BCB4-BD26-4B10-B865-87082F290B38} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {F63A63A9-FE09-4225-A293-6A58F9C1E520} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {0F6B5528-642F-4C46-AB4E-15E9B6CE24F9} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {0EA6A975-2934-4837-9932-2328EFE23BFD} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {34CB5A87-0433-4C19-9CB3-E2F58119CDE9} = {0EA6A975-2934-4837-9932-2328EFE23BFD} + {059C8F76-20F9-42BD-A343-64EE3ACF1AF8} = {0F6B5528-642F-4C46-AB4E-15E9B6CE24F9} + {1D2A2B02-BFA8-4E53-9844-88359C5B2671} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {FC352905-BD72-4049-8D32-3CBB9304FDC8} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {004DEF24-7EBB-499D-BD1C-E940AC4E122D} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {277D77B9-8915-41E3-8763-0B66328ADDDA} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {93B64F12-EBDD-46CE-B4FB-0904701F0032} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {AA4D318D-101B-49E7-A4EC-B34165AEDB33} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {B13C9E5F-0E4B-413E-90AE-20B84B100364} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} + {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {17BDCE12-6964-4B87-B2AC-68CE270A3E9A} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} + {B6342174-5436-4846-B43C-39D8E34AE5CF} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {6947034E-C97F-4F78-940F-B6A398E23C9C} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} + {914B3E60-DE19-4827-956F-22080C817820} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A} + EndGlobalSection +EndGlobal diff --git a/DotNetWorker.Samples.sln b/DotNetWorker.Samples.sln new file mode 100644 index 000000000..0ad9f6d84 --- /dev/null +++ b/DotNetWorker.Samples.sln @@ -0,0 +1,84 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9D6603BD-7EA2-4D11-A69C-0D9E01317FD6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionApp", "samples\FunctionApp\FunctionApp.csproj", "{E61EE414-3693-4C83-BDAA-17AB0024D0E6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{42CB3DE7-B070-4BA1-8A7A-ACB237BEAA4E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensions", "samples\Extensions\Extensions.csproj", "{BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomMiddleware", "samples\CustomMiddleware\CustomMiddleware.csproj", "{2A72383A-9C00-41FB-9D29-909EE5E6BFB9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFramework", "samples\EntityFramework\EntityFramework.csproj", "{C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Configuration", "samples\Configuration\Configuration.csproj", "{035AAD56-5F51-476C-8556-0F6CFD6EF1BF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFxWorker", "samples\NetFxWorker\NetFxWorker.csproj", "{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetIntegration", "samples\AspNetIntegration\AspNetIntegration.csproj", "{D2F67410-9933-42E8-B04A-E17634D83A30}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net9FunctionApp", "samples\Net9FunctionApp\Net9FunctionApp.csproj", "{F7F70331-CB38-47D7-9366-F8C5D934D088}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Release|Any CPU.Build.0 = Release|Any CPU + {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Release|Any CPU.Build.0 = Release|Any CPU + {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Release|Any CPU.Build.0 = Release|Any CPU + {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Release|Any CPU.Build.0 = Release|Any CPU + {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Release|Any CPU.Build.0 = Release|Any CPU + {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.Build.0 = Release|Any CPU + {D2F67410-9933-42E8-B04A-E17634D83A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2F67410-9933-42E8-B04A-E17634D83A30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2F67410-9933-42E8-B04A-E17634D83A30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2F67410-9933-42E8-B04A-E17634D83A30}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F70331-CB38-47D7-9366-F8C5D934D088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F70331-CB38-47D7-9366-F8C5D934D088}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F70331-CB38-47D7-9366-F8C5D934D088}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F70331-CB38-47D7-9366-F8C5D934D088}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E61EE414-3693-4C83-BDAA-17AB0024D0E6} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {2A72383A-9C00-41FB-9D29-909EE5E6BFB9} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {035AAD56-5F51-476C-8556-0F6CFD6EF1BF} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {B37E6BAC-F16B-4366-94FB-8B94B52A08C9} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {D2F67410-9933-42E8-B04A-E17634D83A30} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {F7F70331-CB38-47D7-9366-F8C5D934D088} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A} + EndGlobalSection +EndGlobal diff --git a/DotNetWorker.sln b/DotNetWorker.sln index f9615a07d..3f3663791 100644 --- a/DotNetWorker.sln +++ b/DotNetWorker.sln @@ -3,16 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9D6603BD-7EA2-4D11-A69C-0D9E01317FD6}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FD7243E4-BF18-43F8-8744-BA1D17ACF378}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionApp", "samples\FunctionApp\FunctionApp.csproj", "{E61EE414-3693-4C83-BDAA-17AB0024D0E6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker", "src\DotNetWorker\DotNetWorker.csproj", "{2B47EC99-E383-4DFA-9130-DAF40FDA313B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorkerTests", "test\DotNetWorkerTests\DotNetWorkerTests.csproj", "{02D1B220-3DAE-40E7-B5CC-86501756BF92}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sdk", "sdk", "{4B0D77E7-FA83-4FDD-9E67-CC95EAD21348}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{1B6B4B62-686E-44B4-9CEC-710DA34BC09E}" @@ -27,12 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{1B6B4B62 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk", "sdk\Sdk\Sdk.csproj", "{E28BAAEF-4E70-4CAB-8475-759ECBAF1AF5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SdkTests", "test\FunctionMetadataGeneratorTests\SdkTests.csproj", "{5C38C5A3-83A5-4E2F-9B32-204C2225E890}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionMetadataLoaderExtension", "sdk\FunctionMetadataLoaderExtension\FunctionMetadataLoaderExtension.csproj", "{6B64FE04-BA90-49FC-893A-DF12EF15280C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SdkE2ETests", "test\SdkE2ETests\SdkE2ETests.csproj", "{BFB2832E-C3AB-4F09-B285-B24E535EC858}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{42CB3DE7-B070-4BA1-8A7A-ACB237BEAA4E}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -40,115 +30,41 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtility", "test\TestUtility\TestUtility.csproj", "{C30B77A7-4085-422E-AADE-A4322989F5F8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{A7B4FF1E-3DF7-4F28-9333-D0961CDDF702}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http", "extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj", "{1E601406-6923-4CB9-AAD7-928E08906B10}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Abstractions", "extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj", "{B5F66802-0968-4B99-8E97-E42C744CE5CE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage", "extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj", "{D6EAB0C1-491C-4723-B1F3-B6F5461CD359}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Timer", "extensions\Worker.Extensions.Timer\src\Worker.Extensions.Timer.csproj", "{4DBEC557-E5CC-41E9-9319-BC02615CF228}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.CosmosDB", "extensions\Worker.Extensions.CosmosDB\src\Worker.Extensions.CosmosDB.csproj", "{37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.EventGrid", "extensions\Worker.Extensions.EventGrid\src\Worker.Extensions.EventGrid.csproj", "{9C305047-0B81-4DA1-98D4-BC8B391A2297}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.ServiceBus", "extensions\Worker.Extensions.ServiceBus\src\Worker.Extensions.ServiceBus.csproj", "{660B4D9D-081A-4DCA-BD7A-E196F4529BFD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.EventHubs", "extensions\Worker.Extensions.EventHubs\src\Worker.Extensions.EventHubs.csproj", "{4A5770BE-6BB7-448F-A818-DD1450F8CBDC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.RabbitMQ", "extensions\Worker.Extensions.RabbitMQ\src\Worker.Extensions.RabbitMQ.csproj", "{55652CF7-E58B-4564-A76B-D49DC71878DC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.SignalRService", "extensions\Worker.Extensions.SignalRService\src\Worker.Extensions.SignalRService.csproj", "{A9802FBA-CF59-462B-88A0-1D7D0D19C748}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.SendGrid", "extensions\Worker.Extensions.SendGrid\src\Worker.Extensions.SendGrid.csproj", "{59EB5B01-4756-48BB-8B37-70D29E3BDFB6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Warmup", "extensions\Worker.Extensions.Warmup\src\Worker.Extensions.Warmup.csproj", "{BDA0BCB4-BD26-4B10-B865-87082F290B38}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Kafka", "extensions\Worker.Extensions.Kafka\src\Worker.Extensions.Kafka.csproj", "{F63A63A9-FE09-4225-A293-6A58F9C1E520}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Analyzers", "sdk\Sdk.Analyzers\Sdk.Analyzers.csproj", "{055D602D-D2B3-416B-AC59-1972D832032A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Analyzers.Tests", "test\Sdk.Analyzers.Tests\Sdk.Analyzers.Tests.csproj", "{A75EA1E1-2801-460C-87C0-DE6A82D30851}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{083592CA-7DAB-44CE-8979-44FAFA46AEC3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Worker", "Worker", "{0F6B5528-642F-4C46-AB4E-15E9B6CE24F9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{B5821230-6E0A-4535-88A9-ED31B6F07596}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{0EA6A975-2934-4837-9932-2328EFE23BFD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2EApp", "test\E2ETests\E2EApps\E2EApp\E2EApp.csproj", "{34CB5A87-0433-4C19-9CB3-E2F58119CDE9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETests", "test\E2ETests\E2ETests\E2ETests.csproj", "{059C8F76-20F9-42BD-A343-64EE3ACF1AF8}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.Grpc", "src\DotNetWorker.Grpc\DotNetWorker.Grpc.csproj", "{248ACC90-D4F9-432B-B7B9-341EF00FA072}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.Core", "src\DotNetWorker.Core\DotNetWorker.Core.csproj", "{23AAA8FA-5B51-49BF-8021-148C8031A321}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Extensions", "samples\Extensions\Extensions.csproj", "{BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomMiddleware", "samples\CustomMiddleware\CustomMiddleware.csproj", "{2A72383A-9C00-41FB-9D29-909EE5E6BFB9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFramework", "samples\EntityFramework\EntityFramework.csproj", "{C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Configuration", "samples\Configuration\Configuration.csproj", "{035AAD56-5F51-476C-8556-0F6CFD6EF1BF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Queues", "extensions\Worker.Extensions.Storage.Queues\src\Worker.Extensions.Storage.Queues.csproj", "{1D2A2B02-BFA8-4E53-9844-88359C5B2671}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Storage.Blobs", "extensions\Worker.Extensions.Storage.Blobs\src\Worker.Extensions.Storage.Blobs.csproj", "{FC352905-BD72-4049-8D32-3CBB9304FDC8}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generators", "sdk\Sdk.Generators\Sdk.Generators.csproj", "{F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generator.Tests", "test\Sdk.Generator.Tests\Sdk.Generator.Tests.csproj", "{18A09B24-8646-40A6-BD85-2773AF567453}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Sample", "test\Worker.Extensions.Sample\Worker.Extensions.Sample.csproj", "{9E23C9B2-7C0A-4F09-987F-0E5BDC8BE28C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SampleExtensions", "SampleExtensions", "{922A387F-8595-4C74-ABF1-AEFF9530950C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Sample-IncorrectImplementation", "test\Worker.Extensions.Sample-IncorrectImplementation\Worker.Extensions.Sample-IncorrectImplementation.csproj", "{22FCE0DF-65FE-4650-8202-765832C40E6D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetFxWorker", "samples\NetFxWorker\NetFxWorker.csproj", "{B37E6BAC-F16B-4366-94FB-8B94B52A08C9}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.ApplicationInsights", "src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj", "{65DE66B6-568F-46AC-8F0D-C79A02F48214}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Tables", "extensions\Worker.Extensions.Tables\src\Worker.Extensions.Tables.csproj", "{004DEF24-7EBB-499D-BD1C-E940AC4E122D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Shared", "extensions\Worker.Extensions.Shared\Worker.Extensions.Shared.csproj", "{277D77B9-8915-41E3-8763-0B66328ADDDA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Rpc", "extensions\Worker.Extensions.Rpc\src\Worker.Extensions.Rpc.csproj", "{93B64F12-EBDD-46CE-B4FB-0904701F0032}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{AA4D318D-101B-49E7-A4EC-B34165AEDB33}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Rpc.Tests", "test\Worker.Extensions.Rpc.Tests\Worker.Extensions.Rpc.Tests.csproj", "{B13C9E5F-0E4B-413E-90AE-20B84B100364}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http.AspNetCore", "extensions\Worker.Extensions.Http.AspNetCore\src\Worker.Extensions.Http.AspNetCore.csproj", "{1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.OpenTelemetry", "src\DotNetWorker.OpenTelemetry\DotNetWorker.OpenTelemetry.csproj", "{82157559-DF60-496D-817F-84B34CFF76FD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.SignalRService.Tests", "test\Worker.Extensions.SignalRService.Tests\Worker.Extensions.SignalRService.Tests.csproj", "{286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.OpenTelemetry.Tests", "test\DotNetWorker.OpenTelemetry.Tests\DotNetWorker.OpenTelemetry.Tests.csproj", "{9AE6E00C-5E6F-4615-9C69-464E9B208E8C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Tests", "test\Worker.Extensions.Tests\Worker.Extensions.Tests.csproj", "{17BDCE12-6964-4B87-B2AC-68CE270A3E9A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorkerTests", "test\DotNetWorkerTests\DotNetWorkerTests.csproj", "{B0A6867D-2C35-4BF1-892E-CE84795525BD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetIntegration", "samples\AspNetIntegration\AspNetIntegration.csproj", "{D2F67410-9933-42E8-B04A-E17634D83A30}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Analyzers.Tests", "test\Sdk.Analyzers.Tests\Sdk.Analyzers.Tests.csproj", "{C05499A2-0232-4F73-A6CA-043F0B26C485}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependentAssemblyWithFunctions", "test\DependentAssemblyWithFunctions\DependentAssemblyWithFunctions.csproj", "{AB6E1E7A-0D2C-4086-9487-566887C1E753}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Generator.Tests", "test\Sdk.Generator.Tests\Sdk.Generator.Tests.csproj", "{9DF7E7E0-F669-4140-AE40-1BE53F0F6CF6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Http.AspNetCore.Tests", "test\extensions\Worker.Extensions.Http.AspNetCore.Tests\Worker.Extensions.Http.AspNetCore.Tests.csproj", "{D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sdk", "sdk", "{E785547C-7546-469F-827C-FDF999D5D7E8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Shared.Tests", "test\Worker.Extensions.Shared.Tests\Worker.Extensions.Shared.Tests.csproj", "{B6342174-5436-4846-B43C-39D8E34AE5CF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SampleExtensions", "SampleExtensions", "{93473E6C-2352-497F-85DA-36E01B9579F0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net7Worker", "samples\Net7Worker\Net7Worker.csproj", "{BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Sample", "test\Worker.Extensions.Sample\Worker.Extensions.Sample.csproj", "{B2EEC775-6185-4094-B3B5-767E11EA8B36}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependentAssemblyWithFunctions.NetStandard", "test\DependentAssemblyWithFunctions.NetStandard\DependentAssemblyWithFunctions.NetStandard.csproj", "{198DA072-F94F-4585-A744-97B3DAC21686}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Sample-IncorrectImplementation", "test\Worker.Extensions.Sample-IncorrectImplementation\Worker.Extensions.Sample-IncorrectImplementation.csproj", "{AFF31023-C302-4FE9-829C-5D219DFCDFD5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.OpenTelemetry", "src\DotNetWorker.OpenTelemetry\DotNetWorker.OpenTelemetry.csproj", "{82157559-DF60-496D-817F-84B34CFF76FD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.OpenTelemetry.Tests", "test\DotNetWorker.OpenTelemetry.Tests\DotNetWorker.OpenTelemetry.Tests.csproj", "{9AE6E00C-5E6F-4615-9C69-464E9B208E8C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependentAssemblyWithFunctions", "test\DependentAssemblyWithFunctions\DependentAssemblyWithFunctions.csproj", "{BE88B7F0-F0DF-4EC4-8312-EDCB61810FA4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Timer.Tests", "test\extensions\Worker.Extensions.Timer.Tests\Worker.Extensions.Timer.Tests.csproj", "{6947034E-C97F-4F78-940F-B6A398E23C9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependentAssemblyWithFunctions.NetStandard", "test\DependentAssemblyWithFunctions.NetStandard\DependentAssemblyWithFunctions.NetStandard.csproj", "{429D067C-0846-40EF-A264-AB0C5D551CB0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.Extensions.Http.AspNetCore.Analyzers", "extensions\Worker.Extensions.Http.AspNetCore.Analyzers\src\Worker.Extensions.Http.AspNetCore.Analyzers.csproj", "{63C70590-7F4A-4D54-9157-7FEDE0F8650D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdk.E2ETests", "test\Sdk.E2ETests\Sdk.E2ETests.csproj", "{750993F6-4E3B-411B-9471-74CEA4F9C23A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -156,106 +72,26 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E61EE414-3693-4C83-BDAA-17AB0024D0E6}.Release|Any CPU.Build.0 = Release|Any CPU {2B47EC99-E383-4DFA-9130-DAF40FDA313B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B47EC99-E383-4DFA-9130-DAF40FDA313B}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B47EC99-E383-4DFA-9130-DAF40FDA313B}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B47EC99-E383-4DFA-9130-DAF40FDA313B}.Release|Any CPU.Build.0 = Release|Any CPU - {02D1B220-3DAE-40E7-B5CC-86501756BF92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02D1B220-3DAE-40E7-B5CC-86501756BF92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02D1B220-3DAE-40E7-B5CC-86501756BF92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02D1B220-3DAE-40E7-B5CC-86501756BF92}.Release|Any CPU.Build.0 = Release|Any CPU {E28BAAEF-4E70-4CAB-8475-759ECBAF1AF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E28BAAEF-4E70-4CAB-8475-759ECBAF1AF5}.Debug|Any CPU.Build.0 = Debug|Any CPU {E28BAAEF-4E70-4CAB-8475-759ECBAF1AF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E28BAAEF-4E70-4CAB-8475-759ECBAF1AF5}.Release|Any CPU.Build.0 = Release|Any CPU - {5C38C5A3-83A5-4E2F-9B32-204C2225E890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C38C5A3-83A5-4E2F-9B32-204C2225E890}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C38C5A3-83A5-4E2F-9B32-204C2225E890}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C38C5A3-83A5-4E2F-9B32-204C2225E890}.Release|Any CPU.Build.0 = Release|Any CPU {6B64FE04-BA90-49FC-893A-DF12EF15280C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6B64FE04-BA90-49FC-893A-DF12EF15280C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B64FE04-BA90-49FC-893A-DF12EF15280C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B64FE04-BA90-49FC-893A-DF12EF15280C}.Release|Any CPU.Build.0 = Release|Any CPU - {BFB2832E-C3AB-4F09-B285-B24E535EC858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFB2832E-C3AB-4F09-B285-B24E535EC858}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFB2832E-C3AB-4F09-B285-B24E535EC858}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFB2832E-C3AB-4F09-B285-B24E535EC858}.Release|Any CPU.Build.0 = Release|Any CPU {C30B77A7-4085-422E-AADE-A4322989F5F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C30B77A7-4085-422E-AADE-A4322989F5F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C30B77A7-4085-422E-AADE-A4322989F5F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {C30B77A7-4085-422E-AADE-A4322989F5F8}.Release|Any CPU.Build.0 = Release|Any CPU - {1E601406-6923-4CB9-AAD7-928E08906B10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E601406-6923-4CB9-AAD7-928E08906B10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E601406-6923-4CB9-AAD7-928E08906B10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E601406-6923-4CB9-AAD7-928E08906B10}.Release|Any CPU.Build.0 = Release|Any CPU - {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5F66802-0968-4B99-8E97-E42C744CE5CE}.Release|Any CPU.Build.0 = Release|Any CPU - {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D6EAB0C1-491C-4723-B1F3-B6F5461CD359}.Release|Any CPU.Build.0 = Release|Any CPU - {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4DBEC557-E5CC-41E9-9319-BC02615CF228}.Release|Any CPU.Build.0 = Release|Any CPU - {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Debug|Any CPU.Build.0 = Debug|Any CPU - {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Release|Any CPU.ActiveCfg = Release|Any CPU - {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882}.Release|Any CPU.Build.0 = Release|Any CPU - {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9C305047-0B81-4DA1-98D4-BC8B391A2297}.Release|Any CPU.Build.0 = Release|Any CPU - {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {660B4D9D-081A-4DCA-BD7A-E196F4529BFD}.Release|Any CPU.Build.0 = Release|Any CPU - {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A5770BE-6BB7-448F-A818-DD1450F8CBDC}.Release|Any CPU.Build.0 = Release|Any CPU - {55652CF7-E58B-4564-A76B-D49DC71878DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55652CF7-E58B-4564-A76B-D49DC71878DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55652CF7-E58B-4564-A76B-D49DC71878DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55652CF7-E58B-4564-A76B-D49DC71878DC}.Release|Any CPU.Build.0 = Release|Any CPU - {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9802FBA-CF59-462B-88A0-1D7D0D19C748}.Release|Any CPU.Build.0 = Release|Any CPU - {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59EB5B01-4756-48BB-8B37-70D29E3BDFB6}.Release|Any CPU.Build.0 = Release|Any CPU - {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BDA0BCB4-BD26-4B10-B865-87082F290B38}.Release|Any CPU.Build.0 = Release|Any CPU - {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F63A63A9-FE09-4225-A293-6A58F9C1E520}.Release|Any CPU.Build.0 = Release|Any CPU {055D602D-D2B3-416B-AC59-1972D832032A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {055D602D-D2B3-416B-AC59-1972D832032A}.Debug|Any CPU.Build.0 = Debug|Any CPU {055D602D-D2B3-416B-AC59-1972D832032A}.Release|Any CPU.ActiveCfg = Release|Any CPU {055D602D-D2B3-416B-AC59-1972D832032A}.Release|Any CPU.Build.0 = Release|Any CPU - {A75EA1E1-2801-460C-87C0-DE6A82D30851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A75EA1E1-2801-460C-87C0-DE6A82D30851}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A75EA1E1-2801-460C-87C0-DE6A82D30851}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A75EA1E1-2801-460C-87C0-DE6A82D30851}.Release|Any CPU.Build.0 = Release|Any CPU - {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34CB5A87-0433-4C19-9CB3-E2F58119CDE9}.Release|Any CPU.Build.0 = Release|Any CPU - {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {059C8F76-20F9-42BD-A343-64EE3ACF1AF8}.Release|Any CPU.Build.0 = Release|Any CPU {248ACC90-D4F9-432B-B7B9-341EF00FA072}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {248ACC90-D4F9-432B-B7B9-341EF00FA072}.Debug|Any CPU.Build.0 = Debug|Any CPU {248ACC90-D4F9-432B-B7B9-341EF00FA072}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -264,106 +100,14 @@ Global {23AAA8FA-5B51-49BF-8021-148C8031A321}.Debug|Any CPU.Build.0 = Debug|Any CPU {23AAA8FA-5B51-49BF-8021-148C8031A321}.Release|Any CPU.ActiveCfg = Release|Any CPU {23AAA8FA-5B51-49BF-8021-148C8031A321}.Release|Any CPU.Build.0 = Release|Any CPU - {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4}.Release|Any CPU.Build.0 = Release|Any CPU - {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A72383A-9C00-41FB-9D29-909EE5E6BFB9}.Release|Any CPU.Build.0 = Release|Any CPU - {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779}.Release|Any CPU.Build.0 = Release|Any CPU - {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {035AAD56-5F51-476C-8556-0F6CFD6EF1BF}.Release|Any CPU.Build.0 = Release|Any CPU - {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D2A2B02-BFA8-4E53-9844-88359C5B2671}.Release|Any CPU.Build.0 = Release|Any CPU - {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC352905-BD72-4049-8D32-3CBB9304FDC8}.Release|Any CPU.Build.0 = Release|Any CPU {F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}.Debug|Any CPU.Build.0 = Debug|Any CPU {F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}.Release|Any CPU.ActiveCfg = Release|Any CPU {F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E}.Release|Any CPU.Build.0 = Release|Any CPU - {18A09B24-8646-40A6-BD85-2773AF567453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {18A09B24-8646-40A6-BD85-2773AF567453}.Debug|Any CPU.Build.0 = Debug|Any CPU - {18A09B24-8646-40A6-BD85-2773AF567453}.Release|Any CPU.ActiveCfg = Release|Any CPU - {18A09B24-8646-40A6-BD85-2773AF567453}.Release|Any CPU.Build.0 = Release|Any CPU - {9E23C9B2-7C0A-4F09-987F-0E5BDC8BE28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E23C9B2-7C0A-4F09-987F-0E5BDC8BE28C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E23C9B2-7C0A-4F09-987F-0E5BDC8BE28C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E23C9B2-7C0A-4F09-987F-0E5BDC8BE28C}.Release|Any CPU.Build.0 = Release|Any CPU - {22FCE0DF-65FE-4650-8202-765832C40E6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22FCE0DF-65FE-4650-8202-765832C40E6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22FCE0DF-65FE-4650-8202-765832C40E6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22FCE0DF-65FE-4650-8202-765832C40E6D}.Release|Any CPU.Build.0 = Release|Any CPU - {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B37E6BAC-F16B-4366-94FB-8B94B52A08C9}.Release|Any CPU.Build.0 = Release|Any CPU {65DE66B6-568F-46AC-8F0D-C79A02F48214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {65DE66B6-568F-46AC-8F0D-C79A02F48214}.Debug|Any CPU.Build.0 = Debug|Any CPU {65DE66B6-568F-46AC-8F0D-C79A02F48214}.Release|Any CPU.ActiveCfg = Release|Any CPU {65DE66B6-568F-46AC-8F0D-C79A02F48214}.Release|Any CPU.Build.0 = Release|Any CPU - {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {004DEF24-7EBB-499D-BD1C-E940AC4E122D}.Release|Any CPU.Build.0 = Release|Any CPU - {277D77B9-8915-41E3-8763-0B66328ADDDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {277D77B9-8915-41E3-8763-0B66328ADDDA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {277D77B9-8915-41E3-8763-0B66328ADDDA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {277D77B9-8915-41E3-8763-0B66328ADDDA}.Release|Any CPU.Build.0 = Release|Any CPU - {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Release|Any CPU.Build.0 = Release|Any CPU - {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B13C9E5F-0E4B-413E-90AE-20B84B100364}.Release|Any CPU.Build.0 = Release|Any CPU - {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A}.Release|Any CPU.Build.0 = Release|Any CPU - {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2}.Release|Any CPU.Build.0 = Release|Any CPU - {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17BDCE12-6964-4B87-B2AC-68CE270A3E9A}.Release|Any CPU.Build.0 = Release|Any CPU - {D2F67410-9933-42E8-B04A-E17634D83A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D2F67410-9933-42E8-B04A-E17634D83A30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2F67410-9933-42E8-B04A-E17634D83A30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D2F67410-9933-42E8-B04A-E17634D83A30}.Release|Any CPU.Build.0 = Release|Any CPU - {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Release|Any CPU.Build.0 = Release|Any CPU - {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4}.Release|Any CPU.Build.0 = Release|Any CPU - {B6342174-5436-4846-B43C-39D8E34AE5CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6342174-5436-4846-B43C-39D8E34AE5CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6342174-5436-4846-B43C-39D8E34AE5CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6342174-5436-4846-B43C-39D8E34AE5CF}.Release|Any CPU.Build.0 = Release|Any CPU - {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF}.Release|Any CPU.Build.0 = Release|Any CPU - {198DA072-F94F-4585-A744-97B3DAC21686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {198DA072-F94F-4585-A744-97B3DAC21686}.Debug|Any CPU.Build.0 = Debug|Any CPU - {198DA072-F94F-4585-A744-97B3DAC21686}.Release|Any CPU.ActiveCfg = Release|Any CPU - {198DA072-F94F-4585-A744-97B3DAC21686}.Release|Any CPU.Build.0 = Release|Any CPU {82157559-DF60-496D-817F-84B34CFF76FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82157559-DF60-496D-817F-84B34CFF76FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {82157559-DF60-496D-817F-84B34CFF76FD}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -372,80 +116,64 @@ Global {9AE6E00C-5E6F-4615-9C69-464E9B208E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {9AE6E00C-5E6F-4615-9C69-464E9B208E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AE6E00C-5E6F-4615-9C69-464E9B208E8C}.Release|Any CPU.Build.0 = Release|Any CPU - {6947034E-C97F-4F78-940F-B6A398E23C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6947034E-C97F-4F78-940F-B6A398E23C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6947034E-C97F-4F78-940F-B6A398E23C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6947034E-C97F-4F78-940F-B6A398E23C9C}.Release|Any CPU.Build.0 = Release|Any CPU - {63C70590-7F4A-4D54-9157-7FEDE0F8650D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63C70590-7F4A-4D54-9157-7FEDE0F8650D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63C70590-7F4A-4D54-9157-7FEDE0F8650D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63C70590-7F4A-4D54-9157-7FEDE0F8650D}.Release|Any CPU.Build.0 = Release|Any CPU + {B0A6867D-2C35-4BF1-892E-CE84795525BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0A6867D-2C35-4BF1-892E-CE84795525BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0A6867D-2C35-4BF1-892E-CE84795525BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0A6867D-2C35-4BF1-892E-CE84795525BD}.Release|Any CPU.Build.0 = Release|Any CPU + {C05499A2-0232-4F73-A6CA-043F0B26C485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C05499A2-0232-4F73-A6CA-043F0B26C485}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C05499A2-0232-4F73-A6CA-043F0B26C485}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C05499A2-0232-4F73-A6CA-043F0B26C485}.Release|Any CPU.Build.0 = Release|Any CPU + {9DF7E7E0-F669-4140-AE40-1BE53F0F6CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DF7E7E0-F669-4140-AE40-1BE53F0F6CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DF7E7E0-F669-4140-AE40-1BE53F0F6CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DF7E7E0-F669-4140-AE40-1BE53F0F6CF6}.Release|Any CPU.Build.0 = Release|Any CPU + {B2EEC775-6185-4094-B3B5-767E11EA8B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2EEC775-6185-4094-B3B5-767E11EA8B36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2EEC775-6185-4094-B3B5-767E11EA8B36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2EEC775-6185-4094-B3B5-767E11EA8B36}.Release|Any CPU.Build.0 = Release|Any CPU + {AFF31023-C302-4FE9-829C-5D219DFCDFD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFF31023-C302-4FE9-829C-5D219DFCDFD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFF31023-C302-4FE9-829C-5D219DFCDFD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFF31023-C302-4FE9-829C-5D219DFCDFD5}.Release|Any CPU.Build.0 = Release|Any CPU + {BE88B7F0-F0DF-4EC4-8312-EDCB61810FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE88B7F0-F0DF-4EC4-8312-EDCB61810FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE88B7F0-F0DF-4EC4-8312-EDCB61810FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE88B7F0-F0DF-4EC4-8312-EDCB61810FA4}.Release|Any CPU.Build.0 = Release|Any CPU + {429D067C-0846-40EF-A264-AB0C5D551CB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {429D067C-0846-40EF-A264-AB0C5D551CB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {429D067C-0846-40EF-A264-AB0C5D551CB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {429D067C-0846-40EF-A264-AB0C5D551CB0}.Release|Any CPU.Build.0 = Release|Any CPU + {750993F6-4E3B-411B-9471-74CEA4F9C23A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {750993F6-4E3B-411B-9471-74CEA4F9C23A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {750993F6-4E3B-411B-9471-74CEA4F9C23A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {750993F6-4E3B-411B-9471-74CEA4F9C23A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {E61EE414-3693-4C83-BDAA-17AB0024D0E6} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} {2B47EC99-E383-4DFA-9130-DAF40FDA313B} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3} - {02D1B220-3DAE-40E7-B5CC-86501756BF92} = {0F6B5528-642F-4C46-AB4E-15E9B6CE24F9} {E28BAAEF-4E70-4CAB-8475-759ECBAF1AF5} = {4B0D77E7-FA83-4FDD-9E67-CC95EAD21348} - {5C38C5A3-83A5-4E2F-9B32-204C2225E890} = {B5821230-6E0A-4535-88A9-ED31B6F07596} {6B64FE04-BA90-49FC-893A-DF12EF15280C} = {4B0D77E7-FA83-4FDD-9E67-CC95EAD21348} - {BFB2832E-C3AB-4F09-B285-B24E535EC858} = {B5821230-6E0A-4535-88A9-ED31B6F07596} {C30B77A7-4085-422E-AADE-A4322989F5F8} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {1E601406-6923-4CB9-AAD7-928E08906B10} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {B5F66802-0968-4B99-8E97-E42C744CE5CE} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {D6EAB0C1-491C-4723-B1F3-B6F5461CD359} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {4DBEC557-E5CC-41E9-9319-BC02615CF228} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {37F71B56-1FC7-4BEC-9AB4-FF57C6BF2882} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {9C305047-0B81-4DA1-98D4-BC8B391A2297} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {660B4D9D-081A-4DCA-BD7A-E196F4529BFD} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {4A5770BE-6BB7-448F-A818-DD1450F8CBDC} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {55652CF7-E58B-4564-A76B-D49DC71878DC} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {A9802FBA-CF59-462B-88A0-1D7D0D19C748} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {59EB5B01-4756-48BB-8B37-70D29E3BDFB6} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {BDA0BCB4-BD26-4B10-B865-87082F290B38} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {F63A63A9-FE09-4225-A293-6A58F9C1E520} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} {055D602D-D2B3-416B-AC59-1972D832032A} = {4B0D77E7-FA83-4FDD-9E67-CC95EAD21348} - {A75EA1E1-2801-460C-87C0-DE6A82D30851} = {B5821230-6E0A-4535-88A9-ED31B6F07596} - {0F6B5528-642F-4C46-AB4E-15E9B6CE24F9} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {B5821230-6E0A-4535-88A9-ED31B6F07596} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {0EA6A975-2934-4837-9932-2328EFE23BFD} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {34CB5A87-0433-4C19-9CB3-E2F58119CDE9} = {0EA6A975-2934-4837-9932-2328EFE23BFD} - {059C8F76-20F9-42BD-A343-64EE3ACF1AF8} = {0F6B5528-642F-4C46-AB4E-15E9B6CE24F9} {248ACC90-D4F9-432B-B7B9-341EF00FA072} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3} {23AAA8FA-5B51-49BF-8021-148C8031A321} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3} - {BC56E506-A2F7-46D4-95DC-BD97E5AF92D4} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} - {2A72383A-9C00-41FB-9D29-909EE5E6BFB9} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} - {C4C0CE7E-6A34-473E-A8FB-7D8FBA765779} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} - {035AAD56-5F51-476C-8556-0F6CFD6EF1BF} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} - {1D2A2B02-BFA8-4E53-9844-88359C5B2671} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {FC352905-BD72-4049-8D32-3CBB9304FDC8} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} {F77CCCE6-2DC3-48AA-8FE8-1B135B76B90E} = {4B0D77E7-FA83-4FDD-9E67-CC95EAD21348} - {18A09B24-8646-40A6-BD85-2773AF567453} = {B5821230-6E0A-4535-88A9-ED31B6F07596} - {9E23C9B2-7C0A-4F09-987F-0E5BDC8BE28C} = {922A387F-8595-4C74-ABF1-AEFF9530950C} - {922A387F-8595-4C74-ABF1-AEFF9530950C} = {B5821230-6E0A-4535-88A9-ED31B6F07596} - {22FCE0DF-65FE-4650-8202-765832C40E6D} = {922A387F-8595-4C74-ABF1-AEFF9530950C} - {B37E6BAC-F16B-4366-94FB-8B94B52A08C9} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} {65DE66B6-568F-46AC-8F0D-C79A02F48214} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3} - {004DEF24-7EBB-499D-BD1C-E940AC4E122D} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {277D77B9-8915-41E3-8763-0B66328ADDDA} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {93B64F12-EBDD-46CE-B4FB-0904701F0032} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {AA4D318D-101B-49E7-A4EC-B34165AEDB33} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {B13C9E5F-0E4B-413E-90AE-20B84B100364} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} - {1F6B7CF6-0CC8-4C7F-825F-74B0BEC1CF0A} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {17BDCE12-6964-4B87-B2AC-68CE270A3E9A} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {D2F67410-9933-42E8-B04A-E17634D83A30} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} - {AB6E1E7A-0D2C-4086-9487-566887C1E753} = {B5821230-6E0A-4535-88A9-ED31B6F07596} - {D8E79B53-9A44-46CC-9D7A-E9494FC8CAF4} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} - {B6342174-5436-4846-B43C-39D8E34AE5CF} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {BE1F79C3-24FA-4BC8-BAB2-C1AD321B13FF} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} - {198DA072-F94F-4585-A744-97B3DAC21686} = {B5821230-6E0A-4535-88A9-ED31B6F07596} {82157559-DF60-496D-817F-84B34CFF76FD} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3} {9AE6E00C-5E6F-4615-9C69-464E9B208E8C} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} - {6947034E-C97F-4F78-940F-B6A398E23C9C} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} - {63C70590-7F4A-4D54-9157-7FEDE0F8650D} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} + {B0A6867D-2C35-4BF1-892E-CE84795525BD} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {C05499A2-0232-4F73-A6CA-043F0B26C485} = {E785547C-7546-469F-827C-FDF999D5D7E8} + {9DF7E7E0-F669-4140-AE40-1BE53F0F6CF6} = {E785547C-7546-469F-827C-FDF999D5D7E8} + {E785547C-7546-469F-827C-FDF999D5D7E8} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} + {93473E6C-2352-497F-85DA-36E01B9579F0} = {E785547C-7546-469F-827C-FDF999D5D7E8} + {B2EEC775-6185-4094-B3B5-767E11EA8B36} = {93473E6C-2352-497F-85DA-36E01B9579F0} + {AFF31023-C302-4FE9-829C-5D219DFCDFD5} = {93473E6C-2352-497F-85DA-36E01B9579F0} + {BE88B7F0-F0DF-4EC4-8312-EDCB61810FA4} = {E785547C-7546-469F-827C-FDF999D5D7E8} + {429D067C-0846-40EF-A264-AB0C5D551CB0} = {E785547C-7546-469F-827C-FDF999D5D7E8} + {750993F6-4E3B-411B-9471-74CEA4F9C23A} = {E785547C-7546-469F-827C-FDF999D5D7E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A} diff --git a/NuGet.Config b/NuGet.Config index dd8b5f55b..b35585178 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -4,6 +4,7 @@ - + + \ No newline at end of file diff --git a/README.md b/README.md index 1375a8cbe..4ecf09044 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Azure Functions Logo](https://raw.githubusercontent.com/Azure/azure-functions-cli/master/src/Azure.Functions.Cli/npm/assets/azure-functions-logo-color-raster.png) +![Azure Functions Logo](https://raw.githubusercontent.com/Azure/azure-functions-cli/refs/heads/main/eng/res/functions.png) |Branch|Status| |---|---| diff --git a/build/Common.props b/build/Common.props index df8a00e68..7f4ac28e4 100644 --- a/build/Common.props +++ b/build/Common.props @@ -20,7 +20,7 @@ $(MSBuildThisFileDirectory)/../key.snk embedded true - true" + true true false diff --git a/build/install-dotnet.yml b/build/install-dotnet.yml deleted file mode 100644 index 1cdaa3c38..000000000 --- a/build/install-dotnet.yml +++ /dev/null @@ -1,20 +0,0 @@ -steps: -# Some tests rely on 6.0.412 existing -- task: UseDotNet@2 - displayName: 'Install .NET6 SDK' - inputs: - packageType: 'sdk' - version: "6.x" - -- task: UseDotNet@2 - displayName: 'Install .NET7 SDK' - inputs: - packageType: 'sdk' - version: "7.x" - -# The SDK we use to build -- task: UseDotNet@2 - displayName: 'Install current .NET SDK' - inputs: - packageType: 'sdk' - useGlobalJson: true diff --git a/eng/build/extensionValidationProjectTemplate.txt b/eng/build/extensionValidationProjectTemplate.txt index c6e2d69dc..db66e460d 100644 --- a/eng/build/extensionValidationProjectTemplate.txt +++ b/eng/build/extensionValidationProjectTemplate.txt @@ -2,6 +2,8 @@ net8.0 Library + false + false diff --git a/eng/ci/code-mirror.yml b/eng/ci/code-mirror.yml index 279f4f289..82b2298fa 100644 --- a/eng/ci/code-mirror.yml +++ b/eng/ci/code-mirror.yml @@ -4,6 +4,9 @@ trigger: # Keep this set limited as appropriate (don't mirror individual user branches). - main - release/* + tags: + include: + - "*" resources: repositories: @@ -13,7 +16,7 @@ resources: ref: refs/tags/release variables: - - template: ci/variables/cfs.yml@eng +- template: ci/variables/cfs.yml@eng extends: template: ci/code-mirror.yml@eng diff --git a/eng/ci/host/official-release.yml b/eng/ci/host/official-release.yml new file mode 100644 index 000000000..dac00071a --- /dev/null +++ b/eng/ci/host/official-release.yml @@ -0,0 +1,106 @@ +pr: none +trigger: none + +resources: + repositories: + - repository: 1es + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + - repository: eng + type: git + name: engineering + ref: refs/tags/release + pipelines: + - pipeline: build + source: dotnet-host.official + +variables: +- template: ci/variables/cfs.yml@eng +- name: artifact_name + value: NugetPackages + readonly: true +- name: drop_path + value: $(Pipeline.Workspace)/build/$(artifact_name) + readonly: true +- name: nuget_feed + value: public/infra + readonly: true +- name: packages_pattern + value: $(drop_path)/*.nupkg;!$(drop_path)/**/*.symbols.nupkg + readonly: true + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1es + parameters: + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + + stages: + - stage: Release + jobs: + - job: Prepare + + templateContext: + type: validationJob + + steps: + - checkout: none + + # validationJob uses retail artifact inputs + - download: build + artifact: $(artifact_name) + + # For dotnet host release, we assume the first package (alphanumerically) in the drop is the one we want to version off of. + # This is a bit of a hack, but it works for our current setup. + # We use a regex to extract the version from the package name and set it as the build number. + - pwsh: | + $ErrorActionPreference = 'Stop' + $packages = Get-ChildItem -Path $(drop_path) -Filter *.nupkg -Recurse + $name = $packages[0].Name + Write-Host "Getting version for $name" + $version = [Regex]::Match($name, '(\d+\.\d+\.\d+(?:\.\d+)?(?:-[\w\.].*)?)(?=\.nupkg$)').Value + Write-Host "##vso[build.updatebuildnumber]$version" + displayName: Get package version + + - job: Approval + dependsOn: Prepare + timeoutInMinutes: 1440 + pool: server + + steps: + - task: ManualValidation@1 + inputs: + notifyUsers: '' # no notification + approvers: '[internal]\Azure Functions Core' + allowApproversToApproveTheirOwnRuns: false + instructions: | + Approve to release packages to: + - feed: $(nuget_feed) + + - job: Publish + displayName: Publish packages + dependsOn: Approval + + templateContext: + type: releaseJob + isProduction: true + + inputs: + - input: pipelineArtifact + targetPath: $(drop_path) + artifactName: $(artifact_name) + pipeline: build + + steps: + - task: 1ES.PublishNuget@1 + displayName: Publish packages + inputs: + packagesToPush: $(packages_pattern) + packageParentPath: $(drop_path) + publishVstsFeed: $(nuget_feed) + nuGetFeedType: internal + allowPackageConflicts: true + publishPackageMetadata: true diff --git a/eng/ci/official-build.yml b/eng/ci/official-build.yml index 63776cde4..e55f81491 100644 --- a/eng/ci/official-build.yml +++ b/eng/ci/official-build.yml @@ -56,7 +56,6 @@ extends: stages: - stage: Build - jobs: - template: /eng/ci/templates/official/jobs/build-artifacts.yml@self diff --git a/eng/ci/official-release.yml b/eng/ci/official-release.yml new file mode 100644 index 000000000..fb9f5368f --- /dev/null +++ b/eng/ci/official-release.yml @@ -0,0 +1,148 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false +- name: packages + displayName: Packages to publish + type: string + default: core + values: + - core + - sdk + - application-insights + - open-telemetry + +pr: none +trigger: none + +resources: + repositories: + - repository: 1es + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + - repository: eng + type: git + name: engineering + ref: refs/tags/release + pipelines: + - pipeline: build + source: dotnet-worker.official + +variables: +- name: artifact_name + value: NugetPackages + +- ${{ if eq(parameters.packages, 'core') }}: + - name: target_folder + value: azure-functions/dotnet/worker + - name: package_name + value: Microsoft.Azure.Functions.Worker + - name: package_pattern + value: | + Microsoft.Azure.Functions.Worker.*.nupkg + !Microsoft.Azure.Functions.Worker.Sdk.*.nupkg + !Microsoft.Azure.Functions.Worker.ApplicationInsights.*.nupkg + !Microsoft.Azure.Functions.Worker.OpenTelemetry.*.nupkg + !*.symbols.nupkg + +- ${{ if eq(parameters.packages, 'sdk') }}: + - name: target_folder + value: azure-functions/dotnet/worker-sdk + - name: package_name + value: Microsoft.Azure.Functions.Worker.Sdk + - name: package_pattern + value: | + Microsoft.Azure.Functions.Worker.Sdk.*.nupkg + !*.symbols.nupkg + +- ${{ if eq(parameters.packages, 'application-insights') }}: + - name: target_folder + value: azure-functions/dotnet/worker-app-insights + - name: package_name + value: Microsoft.Azure.Functions.Worker.ApplicationInsights + - name: package_pattern + value: | + Microsoft.Azure.Functions.Worker.ApplicationInsights.*.nupkg + !*.symbols.nupkg + +- ${{ if eq(parameters.packages, 'open-telemetry') }}: + - name: target_folder + value: azure-functions/dotnet/worker-open-telemetry + - name: package_name + value: Microsoft.Azure.Functions.Worker.OpenTelemetry + - name: package_pattern + value: | + Microsoft.Azure.Functions.Worker.OpenTelemetry.*.nupkg + !*.symbols.nupkg + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1es + parameters: + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + + stages: + - stage: Prepare + jobs: + - job: Prepare + + variables: + - name: drop_path + value: $(Pipeline.Workspace)/build/$(artifact_name) + + templateContext: + type: validationJob + + steps: + - checkout: none + + # validationJob uses retail artifact inputs + - download: build + artifact: $(artifact_name) + + # Our build does not have a custom version number set. To convey the version number to the release stage, + # we parse out the version from the .nupkg in the drop and set that as the build number. The release stage + # can then reference $(Build.BuildVersion) for the target upload folder. + - pwsh: | + $ErrorActionPreference = 'Stop' + + $name = "$(package_name)".Trim() + Write-Host "Getting version for $name" + $package = Get-ChildItem -Path $(drop_path) -Recurse -Filter "$name.?.*.nupkg" + + if ($package.Count -eq 0) { + Write-Host "##vso[task.LogIssue type=error;]Could not find package $name." + exit 1 + } + + if ($package.Count -gt 1) { + Write-Host "##vso[task.LogIssue type=error;]Too many packages matched $name." + exit 1 + } + + $version = $package.Name.Trim("$name.").Trim('.nupkg') + Write-Host "##vso[build.updatebuildnumber]$version" + Write-Host "##vso[build.addbuildtag]${{ parameters.packages }}" + Write-Host "##vso[build.addbuildtag]$version" + displayName: Get package version + + - stage: Release + dependsOn: Prepare + jobs: + - template: /ci/release-nuget-package.yml@eng + parameters: + isProduction: true + approvers: '[internal]\Azure Functions Core' + stagingFeed: public/pre-release + packages: $(package_pattern) + artifact: + name: NugetPackages + pipeline: build + ${{ if eq(parameters.publishToNugetOrg, true) }}: + partnerDrop: + serviceConnection: azure-sdk-partner-drops + targetFolder: $(target_folder)/$(Build.BuildNumber) diff --git a/eng/ci/templates/jobs/run-integration-tests-linux.yml b/eng/ci/templates/jobs/run-integration-tests-linux.yml index 0c34bb056..f2b53e85d 100644 --- a/eng/ci/templates/jobs/run-integration-tests-linux.yml +++ b/eng/ci/templates/jobs/run-integration-tests-linux.yml @@ -20,11 +20,13 @@ jobs: inputs: command: build arguments: '-c Release' - projects: DotNetWorker.sln + projects: | + DotNetWorker.sln + DotNetWorker.Extensions.sln - template: /eng/ci/templates/steps/setup-e2e-tests.yml@self parameters: - DotnetVersion: 'net7' + DotnetVersion: 'net8' UseCoreToolsBuild: $(UseCoreToolsBuildFromIntegrationTests) SkipCosmosDBEmulator: true SkipBuildOnPack: true @@ -36,4 +38,3 @@ jobs: arguments: '--no-build -c Release --filter "FullyQualifiedName~HttpTrigger"' # only run http tests to avoid emulators on Linux projects: | test/**/*Tests.csproj - !test/**/Worker.Extensions.Rpc.Tests.csproj diff --git a/eng/ci/templates/jobs/run-integration-tests-windows.yml b/eng/ci/templates/jobs/run-integration-tests-windows.yml index 65f97aeee..ea07fef37 100644 --- a/eng/ci/templates/jobs/run-integration-tests-windows.yml +++ b/eng/ci/templates/jobs/run-integration-tests-windows.yml @@ -1,5 +1,5 @@ parameters: - name: PoolName +- name: PoolName type: string jobs: @@ -8,8 +8,8 @@ jobs: strategy: matrix: - net7: - dotnetVersion: 'net7' + net8: + dotnetVersion: 'net8' netfx: dotnetVersion: 'netfx' @@ -30,7 +30,9 @@ jobs: inputs: command: build arguments: '-c Release' - projects: DotNetWorker.sln + projects: | + DotNetWorker.sln + DotNetWorker.Extensions.sln - template: /eng/ci/templates/steps/setup-e2e-tests.yml@self parameters: @@ -42,7 +44,20 @@ jobs: displayName: 'Run E2E Tests' inputs: command: test - arguments: -v n --no-build -c Release + arguments: -v n --no-build -c Release --filter "FullyQualifiedName!~Microsoft.Azure.Functions.Worker.E2ETests.AspNetCore" # skip AspNetCore tests + projects: | + **/E2ETests.csproj + **/Sdk.E2ETests.csproj + env: + DOTNET_VERSION: $(dotnetVersion) + + - task: DotNetCoreCLI@2 + displayName: 'Run E2E AspNetCore Tests' + condition: ne(variables['dotnetVersion'], 'netfx') # Skip if dotnetVersion is netfx + inputs: + command: test + arguments: -v n --no-build -c Release --filter "FullyQualifiedName~Microsoft.Azure.Functions.Worker.E2ETests.AspNetCore" # only AspNetCore tests projects: | - **\E2ETests.csproj - **\SdkE2ETests.csproj + **/E2ETests.csproj + env: + DOTNET_VERSION: $(dotnetVersion) diff --git a/eng/ci/templates/jobs/run-unit-tests.yml b/eng/ci/templates/jobs/run-unit-tests.yml index 241359e64..b96ccb0ea 100644 --- a/eng/ci/templates/jobs/run-unit-tests.yml +++ b/eng/ci/templates/jobs/run-unit-tests.yml @@ -1,5 +1,5 @@ parameters: - name: PoolName +- name: PoolName type: string jobs: @@ -30,7 +30,7 @@ jobs: command: test arguments: -v n projects: | - **\DotNetWorkerTests.csproj + **/DotNetWorkerTests.csproj - task: DotNetCoreCLI@2 displayName: OpenTelemetry Tests @@ -38,7 +38,7 @@ jobs: command: test arguments: -v n projects: | - **\DotNetWorker.Opentelemetry.Tests.csproj + **/DotNetWorker.OpenTelemetry.Tests.csproj - task: DotNetCoreCLI@2 displayName: Sdk Tests @@ -46,9 +46,8 @@ jobs: command: test arguments: -v n projects: | - **\SdkTests.csproj - **\Sdk.Analyzers.Tests.csproj - **\Sdk.Generator.Tests.csproj + **/Sdk.Analyzers.Tests.csproj + **/Sdk.Generator.Tests.csproj - task: DotNetCoreCLI@2 displayName: Extension Tests @@ -56,8 +55,8 @@ jobs: command: test arguments: -v n projects: | - **\Worker.Extensions.Http.AspNetCore.Tests.csproj - **\Worker.Extensions.Rpc.Tests.csproj - **\Worker.Extensions.Shared.Tests.csproj - **\Worker.Extensions.SignalRService.Tests.csproj - **\Worker.Extensions.Tests.csproj + **/Worker.Extensions.Http.AspNetCore.Tests.csproj + **/Worker.Extensions.Rpc.Tests.csproj + **/Worker.Extensions.Shared.Tests.csproj + **/Worker.Extensions.SignalRService.Tests.csproj + **/Worker.Extensions.Tests.csproj diff --git a/eng/ci/templates/official/jobs/build-artifacts.yml b/eng/ci/templates/official/jobs/build-artifacts.yml index 887d2912d..671ed8052 100644 --- a/eng/ci/templates/official/jobs/build-artifacts.yml +++ b/eng/ci/templates/official/jobs/build-artifacts.yml @@ -17,7 +17,7 @@ jobs: os: windows variables: - ${{ if and( not(contains(variables['Build.SourceBranch'], '/release/')), not(startsWith(variables['Build.SourceBranch'], 'refs/tags')) ) }}: + ${{ if and( not(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')), not(startsWith(variables['Build.SourceBranch'], 'refs/tags')) ) }}: buildNumberTemp: $(Build.BuildNumber) buildNumber: $[variables.buildNumberTemp] diff --git a/eng/ci/templates/official/jobs/build-host-prelaunch-artifacts.yml b/eng/ci/templates/official/jobs/build-host-prelaunch-artifacts.yml index 9596cc439..ca62400d1 100644 --- a/eng/ci/templates/official/jobs/build-host-prelaunch-artifacts.yml +++ b/eng/ci/templates/official/jobs/build-host-prelaunch-artifacts.yml @@ -12,7 +12,7 @@ jobs: artifact: _preLaunchAppPackages variables: - dotnetVersions: 'net8.0,net7.0,net6.0' + dotnetVersions: 'net9.0,net8.0,net6.0' steps: - template: /eng/ci/templates/steps/install-dotnet.yml@self @@ -25,5 +25,5 @@ jobs: publishWebProjects: false zipAfterPublish: false modifyOutputPath: false - arguments: -c Release -o $(Build.ArtifactStagingDirectory)/_preLaunchAppPackages/${{ version }} -f ${{ version }} -p:UseAppHost=false + arguments: -c Release -o $(Build.ArtifactStagingDirectory)/_preLaunchAppPackages/${{ replace(version, 'net', '') }} -f ${{ version }} -p:UseAppHost=false projects: host/src/PrelaunchApp/App.csproj diff --git a/eng/ci/templates/pipelines/release-extension-packages.yml b/eng/ci/templates/pipelines/release-extension-packages.yml new file mode 100644 index 000000000..708e050ab --- /dev/null +++ b/eng/ci/templates/pipelines/release-extension-packages.yml @@ -0,0 +1,87 @@ +parameters: +- name: publishToNugetOrg + type: boolean +- name: artifactName + type: string + default: NugetPackages +- name: targetFolder + type: string + default: '' + +resources: + repositories: + - repository: 1es + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + - repository: eng + type: git + name: engineering + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1es + parameters: + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + + stages: + + - stage: Prepare + jobs: + - job: Prepare + + variables: + - template: ci/variables/cfs.yml@eng + - name: drop_path + value: $(Pipeline.Workspace)/build/${{ parameters.artifactName }} + + templateContext: + type: validationJob + + steps: + - checkout: none + + # validationJob uses retail artifact inputs + - download: build + artifact: ${{ parameters.artifactName }} + + # For extension releases, we assume the first package (alphanumerically) in the drop is the one we want to version off of. + # This is a bit of a hack, but it works for our current setup. + # We use a regex to extract the version from the package name and set it as the build number. + - pwsh: | + $ErrorActionPreference = 'Stop' + $packages = Get-ChildItem -Path $(drop_path) -Filter *.nupkg -Recurse + $name = $packages[0].Name + Write-Host "Getting version for $name" + $version = [Regex]::Match($name, '(\d+\.\d+\.\d+(?:\.\d+)?(?:-[\w\.].*)?)(?=\.nupkg$)').Value + Write-Host "##vso[build.updatebuildnumber]$version" + displayName: Get package version + + - stage: Release + dependsOn: Prepare + + variables: + - template: ci/variables/cfs.yml@eng + - name: target_folder + readonly: true + ${{ if ne(parameters.targetFolder, '') }}: + value: ${{ parameters.targetFolder }} + ${{ else }}: + value: $[ replace(replace(replace(variables['resources.pipeline.build.pipelineName'], 'extensions.', ''), '.official', ''), '.', '-') ] + + jobs: + - template: /ci/release-nuget-package.yml@eng + parameters: + isProduction: true + approvers: '[internal]\Azure Functions Core' + stagingFeed: public/pre-release + artifact: + name: NugetPackages + pipeline: build + ${{ if eq(parameters.publishToNugetOrg, true) }}: + partnerDrop: + serviceConnection: azure-sdk-partner-drops + targetFolder: azure-functions/dotnet/worker-extensions/$(target_folder)/$(Build.BuildNumber) diff --git a/eng/ci/templates/steps/install-dotnet.yml b/eng/ci/templates/steps/install-dotnet.yml index f373c934f..139c7aeb6 100644 --- a/eng/ci/templates/steps/install-dotnet.yml +++ b/eng/ci/templates/steps/install-dotnet.yml @@ -1,19 +1,15 @@ steps: -- task: UseDotNet@2 # Needed by our projects and CI steps - displayName: Install .NET 6 +# Our tests target net8.0 +- task: UseDotNet@2 + displayName: 'Install .NET8 SDK' inputs: - packageType: sdk - version: 6.x + packageType: 'sdk' + version: "8.x" -- task: UseDotNet@2 # Needed by our projects and CI steps - displayName: Install .NET 7 +# The SDK we use to build +- task: UseDotNet@2 + displayName: 'Install current .NET SDK' inputs: - packageType: sdk - version: 7.x - -- task: UseDotNet@2 # The pinned SDK we use to build - displayName: Install .NET SDK from global.json - inputs: - packageType: sdk + packageType: 'sdk' useGlobalJson: true diff --git a/extensions/Worker.Extensions.Abstractions/ci/official-build.yml b/extensions/Worker.Extensions.Abstractions/ci/official-build.yml index f0187b3b0..4b87a0f57 100644 --- a/extensions/Worker.Extensions.Abstractions/ci/official-build.yml +++ b/extensions/Worker.Extensions.Abstractions/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Abstractions/ci/official-release.yml b/extensions/Worker.Extensions.Abstractions/ci/official-release.yml new file mode 100644 index 000000000..82814651b --- /dev/null +++ b/extensions/Worker.Extensions.Abstractions/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.abstractions.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Abstractions/ci/public-build.yml b/extensions/Worker.Extensions.Abstractions/ci/public-build.yml index 4795d120a..1034d2b13 100644 --- a/extensions/Worker.Extensions.Abstractions/ci/public-build.yml +++ b/extensions/Worker.Extensions.Abstractions/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.CosmosDB/ci/official-build.yml b/extensions/Worker.Extensions.CosmosDB/ci/official-build.yml index abbd4ab68..86938faf6 100644 --- a/extensions/Worker.Extensions.CosmosDB/ci/official-build.yml +++ b/extensions/Worker.Extensions.CosmosDB/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.CosmosDB/ci/official-release.yml b/extensions/Worker.Extensions.CosmosDB/ci/official-release.yml new file mode 100644 index 000000000..543ffb0fa --- /dev/null +++ b/extensions/Worker.Extensions.CosmosDB/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.cosmos.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml b/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml index 70c836505..3e44bd491 100644 --- a/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml +++ b/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.CosmosDB/release_notes.md b/extensions/Worker.Extensions.CosmosDB/release_notes.md index 41b9b3e1f..b9c7c90b7 100644 --- a/extensions/Worker.Extensions.CosmosDB/release_notes.md +++ b/extensions/Worker.Extensions.CosmosDB/release_notes.md @@ -4,7 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB 4.11.0 +### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB <4.14.0> -- Updated `Microsoft.Azure.WebJobs.Extensions.CosmosDB` reference to 4.8.0 -- Updated `Microsoft.Extensions.Azure` dependency to 1.7.5 +- Updates dependency `Microsoft.Azure.WebJobs.Extensions.CosmosDB` to version 4.11.0 (#3191) diff --git a/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs b/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs index aeeb881d0..cc7274201 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/CosmosDBConverter.cs @@ -58,6 +58,10 @@ private async ValueTask ConvertFromBindingDataAsync(ConverterC return ConversionResult.Success(result); } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return ConversionResult.Success(null); + } catch (Exception ex) { return ConversionResult.Failed(ex); diff --git a/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj b/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj index 93bfdf713..1d0b09477 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj +++ b/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj @@ -6,7 +6,7 @@ Azure Cosmos DB extensions for .NET isolated functions - 4.11.0 + 4.14.0 false @@ -14,24 +14,24 @@ + + + + - + + - - - - - - + \ No newline at end of file diff --git a/extensions/Worker.Extensions.EventGrid/ci/official-build.yml b/extensions/Worker.Extensions.EventGrid/ci/official-build.yml index 507328f57..613efd388 100644 --- a/extensions/Worker.Extensions.EventGrid/ci/official-build.yml +++ b/extensions/Worker.Extensions.EventGrid/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.EventGrid/ci/official-release.yml b/extensions/Worker.Extensions.EventGrid/ci/official-release.yml new file mode 100644 index 000000000..f8d006ca8 --- /dev/null +++ b/extensions/Worker.Extensions.EventGrid/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.eventgrid.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.EventGrid/ci/public-build.yml b/extensions/Worker.Extensions.EventGrid/ci/public-build.yml index d95e3f1e2..ebb41e23c 100644 --- a/extensions/Worker.Extensions.EventGrid/ci/public-build.yml +++ b/extensions/Worker.Extensions.EventGrid/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.EventGrid/release_notes.md b/extensions/Worker.Extensions.EventGrid/release_notes.md index 5adb0df77..755c456b7 100644 --- a/extensions/Worker.Extensions.EventGrid/release_notes.md +++ b/extensions/Worker.Extensions.EventGrid/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.EventGrid 3.4.2 +### Microsoft.Azure.Functions.Worker.Extensions.EventGrid -- Updated `Microsoft.Azure.WebJobs.Extensions.EventGrid` reference to 3.4.2 +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj b/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj index 35443895d..d4f94c919 100644 --- a/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj +++ b/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj @@ -6,7 +6,7 @@ Azure Event Grid extensions for .NET isolated functions - 3.4.2 + 3.6.0 false @@ -16,15 +16,15 @@ - - + + - + \ No newline at end of file diff --git a/extensions/Worker.Extensions.EventHubs/ci/official-build.yml b/extensions/Worker.Extensions.EventHubs/ci/official-build.yml index 27e2f92a4..8c9ac6cc5 100644 --- a/extensions/Worker.Extensions.EventHubs/ci/official-build.yml +++ b/extensions/Worker.Extensions.EventHubs/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.EventHubs/ci/official-release.yml b/extensions/Worker.Extensions.EventHubs/ci/official-release.yml new file mode 100644 index 000000000..7ea6fd52e --- /dev/null +++ b/extensions/Worker.Extensions.EventHubs/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.eventhubs.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.EventHubs/ci/public-build.yml b/extensions/Worker.Extensions.EventHubs/ci/public-build.yml index ef68d1e36..5908bdc6d 100644 --- a/extensions/Worker.Extensions.EventHubs/ci/public-build.yml +++ b/extensions/Worker.Extensions.EventHubs/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.EventHubs/release_notes.md b/extensions/Worker.Extensions.EventHubs/release_notes.md index 3d9324175..c01c394dc 100644 --- a/extensions/Worker.Extensions.EventHubs/release_notes.md +++ b/extensions/Worker.Extensions.EventHubs/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.EventHubs 6.3.6 +### Microsoft.Azure.Functions.Worker.Extensions.EventHubs 6.5.1 -- Updated `Microsoft.Extensions.Azure` reference to 1.7.5 +- updating `Azure.Messaging.EventHubs` to 5.12.2 (#3189) diff --git a/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj b/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj index 48fc0247e..b9fdafa8a 100644 --- a/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj +++ b/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj @@ -6,20 +6,20 @@ Azure Event Hubs extensions for .NET isolated functions - 6.3.6 + 6.5.1 - - + - - + + + @@ -27,7 +27,7 @@ - + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/CodeFixForHttpResultAttributeExpected.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/CodeFixForHttpResultAttributeExpected.cs index df004ca11..e38e2ee5d 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/CodeFixForHttpResultAttributeExpected.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/CodeFixForHttpResultAttributeExpected.cs @@ -64,8 +64,18 @@ protected override async Task GetChangedDocumentAsync(CancellationToke var typeNode = root.FindNode(this._diagnostic.Location.SourceSpan) .FirstAncestorOrSelf(); - var typeSymbol = semanticModel.GetSymbolInfo(typeNode).Symbol; + + if (typeSymbol is null) + { + return _document; + } + + if (SymbolUtils.TryUnwrapTaskOfT(typeSymbol, semanticModel, out var innerSymbol)) + { + typeSymbol = innerSymbol; + } + var typeDeclarationSyntaxReference = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault(); if (typeDeclarationSyntaxReference is null) { diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs index 20484ddda..877ffc27a 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs @@ -61,6 +61,16 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) var returnType = methodDeclaration.ReturnType; var returnTypeSymbol = semanticModel.GetTypeInfo(returnType).Type; + if (returnTypeSymbol is null) + { + return; + } + + if (SymbolUtils.TryUnwrapTaskOfT(returnTypeSymbol, semanticModel, out var innerSymbol)) + { + returnTypeSymbol = innerSymbol; + } + if (IsHttpReturnType(returnTypeSymbol, semanticModel)) { return; diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/SymbolUtils.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/SymbolUtils.cs new file mode 100644 index 000000000..c7a1dbe47 --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/SymbolUtils.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore +{ + internal class SymbolUtils + { + private static string TaskWrapperTypeName = "System.Threading.Tasks.Task`1"; + + internal static bool TryUnwrapTaskOfT(ISymbol symbol, SemanticModel semanticModel, out ITypeSymbol resultSymbol) + { + var taskType = semanticModel.Compilation.GetTypeByMetadataName(TaskWrapperTypeName); + + resultSymbol = null; + + if (symbol is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.ConstructedFrom is not null && + SymbolEqualityComparer.Default.Equals(namedTypeSymbol.ConstructedFrom, taskType) && + namedTypeSymbol.TypeArguments.Length == 1) + { + resultSymbol = namedTypeSymbol.TypeArguments[0]; + return true; + } + + return false; + } + } +} diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Worker.Extensions.Http.AspNetCore.Analyzers.csproj b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Worker.Extensions.Http.AspNetCore.Analyzers.csproj index 6d37a744e..ae5e5b0ae 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Worker.Extensions.Http.AspNetCore.Analyzers.csproj +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Worker.Extensions.Http.AspNetCore.Analyzers.csproj @@ -1,7 +1,7 @@  - 1.0.3 + 1.0.4 Library true false diff --git a/extensions/Worker.Extensions.Http.AspNetCore/ci/official-build.yml b/extensions/Worker.Extensions.Http.AspNetCore/ci/official-build.yml index 170ccb457..38abcaf38 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/ci/official-build.yml +++ b/extensions/Worker.Extensions.Http.AspNetCore/ci/official-build.yml @@ -36,8 +36,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Http.AspNetCore/ci/official-release.yml b/extensions/Worker.Extensions.Http.AspNetCore/ci/official-release.yml new file mode 100644 index 000000000..bd711f136 --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.http.aspnetcore.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml b/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml index 6e712e2aa..81e472b94 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml +++ b/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml @@ -44,8 +44,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md index f73515feb..459b597e3 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md +++ b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md @@ -6,7 +6,8 @@ ### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore -- Addressing fix for AspNetCoreResponseCookies cookie defaults (#2811) +- Updating ASP.NET Core integration to support the metadata transformation feature (#3172) ### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Analyzers +- diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs index 4590f6336..73276df24 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text.Json; @@ -17,20 +17,22 @@ internal class FunctionsEndpointDataSource : EndpointDataSource private const string HostJsonFileName = "host.json"; private const string DefaultRoutePrefix = "api"; - private readonly IFunctionMetadataProvider _functionMetadataProvider; + private readonly IFunctionMetadataManager _functionMetadataManager; private readonly object _lock = new(); private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { - PropertyNameCaseInsensitive = true + AllowTrailingCommas = true, + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip, }; private List? _endpoints; - public FunctionsEndpointDataSource(IFunctionMetadataProvider functionMetadataProvider) + public FunctionsEndpointDataSource(IFunctionMetadataManager functionMetadataManager) { - _functionMetadataProvider = functionMetadataProvider ?? throw new ArgumentNullException(nameof(functionMetadataProvider)); + _functionMetadataManager = functionMetadataManager ?? throw new ArgumentNullException(nameof(functionMetadataManager)); } public override IReadOnlyList Endpoints @@ -56,7 +58,7 @@ private List BuildEndpoints() string scriptRoot = Environment.GetEnvironmentVariable(FunctionsApplicationDirectoryKey) ?? throw new InvalidOperationException("Cannot determine script root directory."); - var metadata = _functionMetadataProvider.GetFunctionMetadataAsync(scriptRoot).GetAwaiter().GetResult(); + var metadata = _functionMetadataManager.GetFunctionMetadataAsync(scriptRoot).GetAwaiter().GetResult(); string routePrefix = GetRoutePrefixFromHostJson(scriptRoot) ?? DefaultRoutePrefix; diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsHttpContextExtensions.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsHttpContextExtensions.cs index a1b0bd2a2..aa8c8a835 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsHttpContextExtensions.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsHttpContextExtensions.cs @@ -14,7 +14,7 @@ internal static Task InvokeFunctionAsync(this HttpContext context) { var coordinator = context.RequestServices.GetRequiredService(); context.Request.Headers.TryGetValue(Constants.CorrelationHeader, out StringValues invocationId); - return coordinator.RunFunctionInvocationAsync(invocationId); + return coordinator.RunFunctionInvocationAsync(invocationId!); // will fail later if null. } } } diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/WorkerRequestServicesMiddleware.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/WorkerRequestServicesMiddleware.cs index 8fa0e583d..36073af78 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/WorkerRequestServicesMiddleware.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/WorkerRequestServicesMiddleware.cs @@ -25,7 +25,7 @@ public async Task Invoke(HttpContext context) throw new InvalidOperationException($"Expected correlation id header ('{Constants.CorrelationHeader}') not present"); } - FunctionContext functionContext = await _coordinator.SetHttpContextAsync(invocationId, context); + FunctionContext functionContext = await _coordinator.SetHttpContextAsync(invocationId!, context); // Explicitly set the RequestServices to prevent a new scope from being created internally. // This also prevents the scope from being disposed when the request is complete. We want this to diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/ContextReference.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/ContextReference.cs index ddaac3487..9dcd8b026 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/ContextReference.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/ContextReference.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore { - internal class ContextReference : IDisposable + internal class ContextReference { private readonly TaskCompletionSource _functionStartTask = new(); private readonly TaskCompletionSource _functionCompletionTask = new(); @@ -16,9 +16,6 @@ internal class ContextReference : IDisposable private TaskCompletionSource _httpContextValueSource = new(); private TaskCompletionSource _functionContextValueSource = new(); - private CancellationToken _token; - private CancellationTokenRegistration _tokenRegistration; - public ContextReference(string invocationId) { InvocationId = invocationId; @@ -32,18 +29,6 @@ public ContextReference(string invocationId) public TaskCompletionSource FunctionContextValueSource { get => _functionContextValueSource; set => _functionContextValueSource = value; } - internal void SetCancellationToken(CancellationToken token) - { - _token = token; - _tokenRegistration = _token.Register(() => - { - _functionStartTask.TrySetCanceled(); - _functionCompletionTask.TrySetCanceled(); - _functionContextValueSource.TrySetCanceled(); - _httpContextValueSource.TrySetCanceled(); - }); - } - internal Task InvokeFunctionAsync() { _functionStartTask.SetResult(true); @@ -59,27 +44,12 @@ internal void CompleteFunction() if (_httpContextValueSource.Task.IsCompleted) { - if (_httpContextValueSource.Task.IsCanceled || _token.IsCancellationRequested) - { - _functionCompletionTask.TrySetCanceled(); - } - else - { - _functionCompletionTask.TrySetResult(true); - } + _functionCompletionTask.TrySetResult(true); } else { // we should never reach here b/c the class that calls this needs httpContextValueSource to complete to reach this method } } - - public void Dispose() - { - if (_tokenRegistration != default) - { - _tokenRegistration.Dispose(); - } - } } } diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/DefaultHttpCoordinator.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/DefaultHttpCoordinator.cs index cbe542cdf..9f9a5d69b 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/DefaultHttpCoordinator.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Coordinator/DefaultHttpCoordinator.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; @@ -44,7 +44,6 @@ public async Task SetHttpContextAsync(string invocationId, Http public async Task SetFunctionContextAsync(string invocationId, FunctionContext context) { var contextRef = _contextReferenceList.GetOrAdd(invocationId, static id => new ContextReference(id)); - contextRef.SetCancellationToken(context.CancellationToken); contextRef.FunctionContextValueSource.SetResult(context); _logger.FunctionContextSet(invocationId); @@ -85,7 +84,6 @@ public void CompleteFunctionInvocation(string invocationId) if (_contextReferenceList.TryRemove(invocationId, out var contextRef)) { contextRef.CompleteFunction(); - contextRef.Dispose(); } else { diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsApplicationBuilderAspNetCoreExtensions.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsApplicationBuilderAspNetCoreExtensions.cs new file mode 100644 index 000000000..0e241a282 --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsApplicationBuilderAspNetCoreExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Extensions.Hosting; + +namespace Microsoft.Azure.Functions.Worker.Builder; + +/// +/// ASP.NET Core extensions for . +/// +public static class FunctionsApplicationBuilderAspNetCoreExtensions +{ + /// + /// Configures the worker to use the ASP.NET Core integration, enabling advanced HTTP features. + /// + /// The to configure. + /// The for chaining. + public static FunctionsApplicationBuilder ConfigureFunctionsWebApplication(this FunctionsApplicationBuilder builder) + { + builder.HostBuilder.ConfigureFunctionsWebApplication(); + return builder; + } +} diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs index 83dbaeac0..15afd4bf7 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs @@ -9,8 +9,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Routing; -using Microsoft.Azure.Functions.Worker.Extensions.Http.Converters; using Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Infrastructure; +using Microsoft.Azure.Functions.Worker.Extensions.Http.Converters; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.DependencyInjection; @@ -85,6 +85,9 @@ private static async Task TryHandleHttpResult(object? result, FunctionCont // processing is required. context.GetInvocationResult().Value = null; break; + case AspNetCoreHttpResponseData when !isInvocationResult: + ClearHttpOutputBinding(context); + break; case IResult iResult: await iResult.ExecuteAsync(httpContext); break; @@ -105,6 +108,17 @@ private static Task TryHandleOutputBindingsHttpResult(FunctionContext cont : TryHandleHttpResult(httpOutputBinding.Value, context, httpContext); } + private static void ClearHttpOutputBinding(FunctionContext context) + { + var httpOutputBinding = context.GetOutputBindings() + .FirstOrDefault(a => string.Equals(a.BindingType, HttpBindingType, StringComparison.OrdinalIgnoreCase)); + + if (httpOutputBinding != null) + { + httpOutputBinding.Value = null; + } + } + private static void AddHttpContextToFunctionContext(FunctionContext funcContext, HttpContext httpContext) { funcContext.Items.Add(Constants.HttpContextKey, httpContext); diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/HttpDataModel/AspNetCoreHttpHeadersCollection.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/HttpDataModel/AspNetCoreHttpHeadersCollection.cs index 20e859fe1..6378ce415 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/HttpDataModel/AspNetCoreHttpHeadersCollection.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/HttpDataModel/AspNetCoreHttpHeadersCollection.cs @@ -57,7 +57,7 @@ private Task ProcessResponseStarting() _originalResponseHeaders.Clear(); foreach (var item in ((HeadersEnumerable)this)) { - _originalResponseHeaders.Add(item.Key, item.Value); + _originalResponseHeaders[item.Key] = item.Value; } return Task.CompletedTask; diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Infrastructure/ExtensionTrace.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/Infrastructure/ExtensionTrace.cs index 545cce031..89724adfa 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Infrastructure/ExtensionTrace.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Infrastructure/ExtensionTrace.cs @@ -15,7 +15,9 @@ public ExtensionTrace(ILoggerFactory loggerFactory) _defaultLogger = loggerFactory.CreateLogger("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore"); } - public IDisposable BeginScope(TState state) => _defaultLogger.BeginScope(state); + public IDisposable? BeginScope(TState state) + where TState : notnull + => _defaultLogger.BeginScope(state); public bool IsEnabled(LogLevel logLevel) => _defaultLogger.IsEnabled(logLevel); diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/Properties/AssemblyInfo.cs index 33f6f1705..794added6 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Properties/AssemblyInfo.cs @@ -3,6 +3,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj b/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj index 23d89d2b3..842c74879 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj @@ -6,8 +6,8 @@ ASP.NET Core extensions for .NET isolated functions - 1.3.3 - net6.0 + 2.1.0 + net6.0;net8.0 @@ -15,11 +15,15 @@ + - + + + + diff --git a/extensions/Worker.Extensions.Http/ci/official-build.yml b/extensions/Worker.Extensions.Http/ci/official-build.yml index c6b93d56f..8abadd548 100644 --- a/extensions/Worker.Extensions.Http/ci/official-build.yml +++ b/extensions/Worker.Extensions.Http/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Http/ci/official-release.yml b/extensions/Worker.Extensions.Http/ci/official-release.yml new file mode 100644 index 000000000..c32ffd8b1 --- /dev/null +++ b/extensions/Worker.Extensions.Http/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.http.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Http/ci/public-build.yml b/extensions/Worker.Extensions.Http/ci/public-build.yml index 58336e9f9..011ddfeae 100644 --- a/extensions/Worker.Extensions.Http/ci/public-build.yml +++ b/extensions/Worker.Extensions.Http/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Http/release_notes.md b/extensions/Worker.Extensions.Http/release_notes.md index 8f2985eb5..634fa5eb2 100644 --- a/extensions/Worker.Extensions.Http/release_notes.md +++ b/extensions/Worker.Extensions.Http/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Http +### Microsoft.Azure.Functions.Worker.Extensions.Http 3.3.0 - diff --git a/extensions/Worker.Extensions.Http/src/DefaultFromBodyConversionFeature.cs b/extensions/Worker.Extensions.Http/src/DefaultFromBodyConversionFeature.cs index c98d1950a..fbe9c95ba 100644 --- a/extensions/Worker.Extensions.Http/src/DefaultFromBodyConversionFeature.cs +++ b/extensions/Worker.Extensions.Http/src/DefaultFromBodyConversionFeature.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.IO; using System.Linq; using System.Net.Http.Headers; using System.Threading; @@ -46,44 +47,23 @@ internal class DefaultFromBodyConversionFeature : IFromBodyConversionFeature return ConvertBodyAsync(requestData, context, targetType); } - private static ValueTask ConvertBodyAsync(HttpRequestData requestData, FunctionContext context, Type targetType) + private static async ValueTask ConvertBodyAsync(HttpRequestData requestData, FunctionContext context, Type targetType) => targetType switch { - object? result; - - if (targetType == typeof(string)) - { - result = requestData.ReadAsString(); - } - else if (targetType == typeof(byte[])) - { - result = ReadBytes(requestData, context.CancellationToken); - } - else if (targetType == typeof(Memory)) - { - Memory bytes = ReadBytes(requestData, context.CancellationToken); - result = bytes; - } - else if (HasJsonContentType(requestData)) - { - ObjectSerializer serializer = requestData.FunctionContext.InstanceServices.GetService>()?.Value?.Serializer - ?? throw new InvalidOperationException("A serializer is not configured for the worker."); - - result = serializer.Deserialize(requestData.Body, targetType, context.CancellationToken); - } - else - { - throw new InvalidOperationException($"The type '{targetType}' is not supported by the '{nameof(DefaultFromBodyConversionFeature)}'."); - } - - return new ValueTask(result); - } - - private static byte[] ReadBytes(HttpRequestData requestData, CancellationToken cancellationToken) + _ when targetType == typeof(string) => await requestData.ReadAsStringAsync(), + _ when targetType == typeof(byte[]) => await ReadBytesAsync(requestData), + _ when targetType == typeof(Memory) => new Memory(await ReadBytesAsync(requestData)), + _ when HasJsonContentType(requestData) => + await (requestData.FunctionContext.InstanceServices.GetService>()?.Value?.Serializer + ?? throw new InvalidOperationException("A serializer is not configured for the worker.")) + .DeserializeAsync(requestData.Body, targetType, CancellationToken.None), + _ => throw new InvalidOperationException($"The type '{targetType}' is not supported by the '{nameof(DefaultFromBodyConversionFeature)}'.") + }; + + private static async Task ReadBytesAsync(HttpRequestData requestData) { - var bytes = new byte[requestData.Body.Length]; - requestData.Body.Read(bytes, 0, bytes.Length); - - return bytes; + using var memoryStream = new MemoryStream(); + await requestData.Body.CopyToAsync(memoryStream); + return memoryStream.ToArray(); } private static bool HasJsonContentType(HttpRequestData request) diff --git a/extensions/Worker.Extensions.Http/src/Worker.Extensions.Http.csproj b/extensions/Worker.Extensions.Http/src/Worker.Extensions.Http.csproj index 7aa205d47..25f4cdb2c 100644 --- a/extensions/Worker.Extensions.Http/src/Worker.Extensions.Http.csproj +++ b/extensions/Worker.Extensions.Http/src/Worker.Extensions.Http.csproj @@ -6,14 +6,17 @@ HTTP extensions for .NET isolated functions - 3.2.0 + 3.3.0 - + + + + diff --git a/extensions/Worker.Extensions.Kafka/ci/official-build.yml b/extensions/Worker.Extensions.Kafka/ci/official-build.yml index 899670150..71f758bfa 100644 --- a/extensions/Worker.Extensions.Kafka/ci/official-build.yml +++ b/extensions/Worker.Extensions.Kafka/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Kafka/ci/official-release.yml b/extensions/Worker.Extensions.Kafka/ci/official-release.yml new file mode 100644 index 000000000..a498aa2f6 --- /dev/null +++ b/extensions/Worker.Extensions.Kafka/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.kafka.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Kafka/ci/public-build.yml b/extensions/Worker.Extensions.Kafka/ci/public-build.yml index 3ddb3878c..b6395af8f 100644 --- a/extensions/Worker.Extensions.Kafka/ci/public-build.yml +++ b/extensions/Worker.Extensions.Kafka/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Kafka/release_notes.md b/extensions/Worker.Extensions.Kafka/release_notes.md index 6bf707d25..e73c591ab 100644 --- a/extensions/Worker.Extensions.Kafka/release_notes.md +++ b/extensions/Worker.Extensions.Kafka/release_notes.md @@ -4,7 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Kafka 4.0.0 +### Microsoft.Azure.Functions.Worker.Extensions.Kafka -- Add OAuthBearer trigger and output Attributes to the dotnet isolated model(#2799) -- Update kafka extension version to 4.0.0(#2799) +- diff --git a/extensions/Worker.Extensions.Kafka/src/KafkaMessageKeyType.cs b/extensions/Worker.Extensions.Kafka/src/KafkaMessageKeyType.cs new file mode 100644 index 000000000..7c98a16c7 --- /dev/null +++ b/extensions/Worker.Extensions.Kafka/src/KafkaMessageKeyType.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// Defines the data type used in kafka extension as enum. + /// + public enum KafkaMessageKeyType + { + Int = 0, + Long, + String, + Binary + } +} diff --git a/extensions/Worker.Extensions.Kafka/src/KafkaOutputAttribute.cs b/extensions/Worker.Extensions.Kafka/src/KafkaOutputAttribute.cs index bb640abdb..9f6af78ed 100644 --- a/extensions/Worker.Extensions.Kafka/src/KafkaOutputAttribute.cs +++ b/extensions/Worker.Extensions.Kafka/src/KafkaOutputAttribute.cs @@ -34,6 +34,20 @@ public KafkaOutputAttribute(string brokerList, string topic) /// public string AvroSchema { get; set; } + /// + /// Gets or sets the Avro schema of message key. + /// Should be used only if a generic record should be generated. + /// + public string KeyAvroSchema { get; set; } + + /// + /// Specifies the data type of the message key. + /// This data type will be used to serialize the key before sending it to the Kafka topic. + /// If KeyAvroSchema is set, this value is ignored and the key will be serialized using Avro. + /// The default type is System.String. + /// + public KafkaMessageKeyType KeyDataType { get; set; } = KafkaMessageKeyType.String; + /// /// Gets or sets the maximum transmit message size in bytes. Default: 1MB /// @@ -123,6 +137,30 @@ public KafkaOutputAttribute(string brokerList, string topic) /// public string SslKeyPassword { get; set; } + /// + /// Client certificate in PEM format. + /// ssl.certificate.pem in librdkafka + /// + public string SslCertificatePEM { get; set; } + + /// + /// Client Private Key in PEM format. + /// ssl.key.pem in librdkafka + /// + public string SslKeyPEM { get; set; } + + /// + /// CA certificate for verifying the broker's certificate in PEM format + /// ssl.ca.pem in librdkafka + /// + public string SslCaPEM { get; set; } + + /// + /// Client certificate and key in PEM format. + /// Additional Configuration for extension as KeyVault supports uploading certificate only with private key. + /// + public string SslCertificateandKeyPEM { get; set; } + /// /// Linger.MS property provides the time between batches of messages /// being sent to cluster. Larger value allows more batching results in high throughput. diff --git a/extensions/Worker.Extensions.Kafka/src/KafkaTriggerAttribute.cs b/extensions/Worker.Extensions.Kafka/src/KafkaTriggerAttribute.cs index 9b9a594e5..5c6cc934e 100644 --- a/extensions/Worker.Extensions.Kafka/src/KafkaTriggerAttribute.cs +++ b/extensions/Worker.Extensions.Kafka/src/KafkaTriggerAttribute.cs @@ -45,6 +45,19 @@ public KafkaTriggerAttribute(string brokerList, string topic) /// public string AvroSchema { get; set; } + /// + /// Gets or sets the Avro schema of message key. + /// Should be used only if a generic record should be generated. + /// + public string KeyAvroSchema { get; set; } + + /// + /// Specifies the data type of the message key that will be deserialized from the Kafka topic. + /// If KeyAvroSchema is set, this value is ignored and the key will be generated as a generic record. + /// The default type is System.String. + /// + public KafkaMessageKeyType KeyDataType { get; set; } = KafkaMessageKeyType.String; + /// /// SASL mechanism to use for authentication. /// Allowed values: Gssapi, Plain, ScramSha256, ScramSha512, OAuthBearer @@ -103,6 +116,31 @@ public KafkaTriggerAttribute(string brokerList, string topic) /// public string SslKeyPassword { get; set; } + /// + /// Client certificate in PEM format. + /// ssl.certificate.pem in librdkafka + /// + public string SslCertificatePEM { get; set; } + + /// + /// Client Private Key in PEM format. + /// ssl.key.pem in librdkafka + /// + public string SslKeyPEM { get; set; } + + /// + /// CA certificate for verifying the broker's certificate in PEM format + /// ssl.ca.pem in librdkafka + /// + public string SslCaPEM { get; set; } + + /// + /// Client certificate and key in PEM format. + /// Additional Configuration for extension as KeyVault supports uploading certificate only with private key. + /// + public string SslCertificateandKeyPEM { get; set; } + + /// /// Maximum number of unprocessed messages a worker is expected to have at an instance. /// When target-based scaling is not disabled, this is used to divide the diff --git a/extensions/Worker.Extensions.Kafka/src/OAuthBearerMethod.cs b/extensions/Worker.Extensions.Kafka/src/OAuthBearerMethod.cs index 47030255d..8e43b3fc0 100644 --- a/extensions/Worker.Extensions.Kafka/src/OAuthBearerMethod.cs +++ b/extensions/Worker.Extensions.Kafka/src/OAuthBearerMethod.cs @@ -15,4 +15,4 @@ public enum OAuthBearerMethod Default, Oidc } -} \ No newline at end of file +} diff --git a/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj b/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj index e78a27be0..f58db4145 100644 --- a/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj +++ b/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj @@ -6,7 +6,7 @@ Kafka extensions for .NET isolated functions - 4.0.0 + 4.1.3 false @@ -21,7 +21,7 @@ - + - \ No newline at end of file + diff --git a/extensions/Worker.Extensions.RabbitMQ/ci/official-build.yml b/extensions/Worker.Extensions.RabbitMQ/ci/official-build.yml index 11d517d22..4566a21bb 100644 --- a/extensions/Worker.Extensions.RabbitMQ/ci/official-build.yml +++ b/extensions/Worker.Extensions.RabbitMQ/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.RabbitMQ/ci/official-release.yml b/extensions/Worker.Extensions.RabbitMQ/ci/official-release.yml new file mode 100644 index 000000000..8e3d3b933 --- /dev/null +++ b/extensions/Worker.Extensions.RabbitMQ/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.rabbitmq.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml b/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml index 7232c3074..c3700a646 100644 --- a/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml +++ b/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj b/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj index 0868de5ac..c3efdc5c9 100644 --- a/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj +++ b/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj @@ -6,7 +6,7 @@ RabbitMQ extensions for .NET isolated functions - 2.0.3 + 2.1.0 false @@ -19,7 +19,7 @@ - + diff --git a/extensions/Worker.Extensions.Rpc/ci/official-build.yml b/extensions/Worker.Extensions.Rpc/ci/official-build.yml index 4dbb8820d..49e4ff463 100644 --- a/extensions/Worker.Extensions.Rpc/ci/official-build.yml +++ b/extensions/Worker.Extensions.Rpc/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Rpc/ci/official-release.yml b/extensions/Worker.Extensions.Rpc/ci/official-release.yml new file mode 100644 index 000000000..3256a7e4c --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.rpc.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Rpc/ci/public-build.yml b/extensions/Worker.Extensions.Rpc/ci/public-build.yml index 84d671352..229b3ed68 100644 --- a/extensions/Worker.Extensions.Rpc/ci/public-build.yml +++ b/extensions/Worker.Extensions.Rpc/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Rpc/release_notes.md b/extensions/Worker.Extensions.Rpc/release_notes.md index 45f46f3e9..3fe81032f 100644 --- a/extensions/Worker.Extensions.Rpc/release_notes.md +++ b/extensions/Worker.Extensions.Rpc/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.1 +### Microsoft.Azure.Functions.Worker.Extensions.Rpc -- Set max message send and receive length on gRPC `CallInvoker`. +- diff --git a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj index b9aa4156f..769980a07 100644 --- a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj +++ b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj @@ -13,9 +13,12 @@ - + + + + diff --git a/extensions/Worker.Extensions.SendGrid/ci/official-build.yml b/extensions/Worker.Extensions.SendGrid/ci/official-build.yml index aa8a963e5..6b2e0b4fc 100644 --- a/extensions/Worker.Extensions.SendGrid/ci/official-build.yml +++ b/extensions/Worker.Extensions.SendGrid/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.SendGrid/ci/official-release.yml b/extensions/Worker.Extensions.SendGrid/ci/official-release.yml new file mode 100644 index 000000000..d1daea0a3 --- /dev/null +++ b/extensions/Worker.Extensions.SendGrid/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.sendgrid.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.SendGrid/ci/public-build.yml b/extensions/Worker.Extensions.SendGrid/ci/public-build.yml index b47181cdb..05afcc87e 100644 --- a/extensions/Worker.Extensions.SendGrid/ci/public-build.yml +++ b/extensions/Worker.Extensions.SendGrid/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.SendGrid/release_notes.md b/extensions/Worker.Extensions.SendGrid/release_notes.md index 791f66a3d..bbdb339a6 100644 --- a/extensions/Worker.Extensions.SendGrid/release_notes.md +++ b/extensions/Worker.Extensions.SendGrid/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.SendGrid +### Microsoft.Azure.Functions.Worker.Extensions.SendGrid 3.1.0 -- +- Update dependency "Microsoft.Azure.WebJobs.Extensions.SendGrid" to v3.1.0 diff --git a/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj b/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj index 44e1f3221..3f324fcf9 100644 --- a/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj +++ b/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj @@ -6,7 +6,7 @@ Azure SendGrid extension for .NET isolated functions - 3.0.3 + 3.1.0 false @@ -19,7 +19,7 @@ - + diff --git a/extensions/Worker.Extensions.ServiceBus/ci/official-build.yml b/extensions/Worker.Extensions.ServiceBus/ci/official-build.yml index 36e723bd5..f1f10b0cf 100644 --- a/extensions/Worker.Extensions.ServiceBus/ci/official-build.yml +++ b/extensions/Worker.Extensions.ServiceBus/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.ServiceBus/ci/official-release.yml b/extensions/Worker.Extensions.ServiceBus/ci/official-release.yml new file mode 100644 index 000000000..97a2cd768 --- /dev/null +++ b/extensions/Worker.Extensions.ServiceBus/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.servicebus.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml b/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml index 3da6a0ad4..0d4ae77fc 100644 --- a/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml +++ b/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.ServiceBus/release_notes.md b/extensions/Worker.Extensions.ServiceBus/release_notes.md index 5b169b47d..c92b7d33c 100644 --- a/extensions/Worker.Extensions.ServiceBus/release_notes.md +++ b/extensions/Worker.Extensions.ServiceBus/release_notes.md @@ -4,7 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.ServiceBus 5.22.0 +### Microsoft.Azure.Functions.Worker.Extensions.ServiceBus -- Updated `Azure.Identity` reference to 1.12.0 -- Updated `Microsoft.Extensions.Azure` to 1.7.5 \ No newline at end of file +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.ServiceBus/src/Constants.cs b/extensions/Worker.Extensions.ServiceBus/src/Constants.cs index 8c63d4124..97e7198ef 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Constants.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Constants.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. namespace Microsoft.Azure.Functions.Worker.Extensions.ServiceBus @@ -8,5 +8,13 @@ internal static class Constants internal const string BinaryContentType = "application/octet-stream"; internal const string BindingSource = "AzureServiceBusReceivedMessage"; + + internal const string SessionId = "SessionId"; + + internal const string SessionIdArray = "SessionIdArray"; + + internal const string SessionActions = "SessionActions"; + + internal const string SessionLockedUntil = "SessionLockedUntil"; } -} \ No newline at end of file +} diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusMessageActions.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusMessageActions.cs index ad8bd4e47..f4b64825c 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusMessageActions.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusMessageActions.cs @@ -81,7 +81,7 @@ public virtual async Task CompleteMessageAsync( await _settlement.CompleteAsync(new() { Locktoken = message.LockToken }, cancellationToken: cancellationToken); } - /// + /// public virtual async Task AbandonMessageAsync( ServiceBusReceivedMessage message, IDictionary? propertiesToModify = default, diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActionsConverter.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActionsConverter.cs index 1a37c3c47..9df288b67 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActionsConverter.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActionsConverter.cs @@ -2,11 +2,13 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Converters; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; +using Microsoft.Azure.Functions.Worker.Extensions.ServiceBus; using Microsoft.Azure.ServiceBus.Grpc; -using System.Text.Json; namespace Microsoft.Azure.Functions.Worker { @@ -15,7 +17,6 @@ namespace Microsoft.Azure.Functions.Worker /// [SupportsDeferredBinding] [SupportedTargetType(typeof(ServiceBusSessionMessageActions))] - [SupportedTargetType(typeof(ServiceBusSessionMessageActions[]))] internal class ServiceBusSessionMessageActionsConverter : IInputConverter { private readonly Settlement.SettlementClient _settlement; @@ -29,27 +30,23 @@ public ValueTask ConvertAsync(ConverterContext context) { try { - var foundSessionId = context.FunctionContext.BindingContext.BindingData.TryGetValue("SessionId", out object? sessionId); - if (!foundSessionId) - { - throw new InvalidOperationException($"Expecting SessionId within binding data and value was not present. Sessions must be enabled when binding to {nameof(ServiceBusSessionMessageActions)}."); - } + var sessionId = ParseSessionIdFromBindingData(context); // Get the sessionLockedUntil property from the SessionActions binding data - var foundSessionActions = context.FunctionContext.BindingContext.BindingData.TryGetValue("SessionActions", out object? sessionActions); + var foundSessionActions = context.FunctionContext.BindingContext.BindingData.TryGetValue(Constants.SessionActions, out object? sessionActions); if (!foundSessionActions) { - throw new InvalidOperationException("Expecting SessionActions within binding data and value was not present."); + throw new InvalidOperationException($"Expecting {Constants.SessionActions} within binding data and value was not present."); } JsonDocument jsonDocument = JsonDocument.Parse(sessionActions!.ToString()); - var foundSessionLockedUntil = jsonDocument.RootElement.TryGetProperty("SessionLockedUntil", out JsonElement sessionLockedUntil); + var foundSessionLockedUntil = jsonDocument.RootElement.TryGetProperty(Constants.SessionLockedUntil, out JsonElement sessionLockedUntil); if (!foundSessionLockedUntil) { - throw new InvalidOperationException("Expecting SessionLockedUntil within binding data of session actions and value was not present."); + throw new InvalidOperationException($"Expecting {Constants.SessionLockedUntil} within binding data of session actions and value was not present."); } - var sessionActionResult = new ServiceBusSessionMessageActions(_settlement, sessionId!.ToString(), sessionLockedUntil.GetDateTimeOffset()); + var sessionActionResult = new ServiceBusSessionMessageActions(_settlement, sessionId, sessionLockedUntil.GetDateTimeOffset()); var result = ConversionResult.Success(sessionActionResult); return new ValueTask(result); } @@ -58,5 +55,32 @@ public ValueTask ConvertAsync(ConverterContext context) return new ValueTask(ConversionResult.Failed(exception)); } } + + private string ParseSessionIdFromBindingData(ConverterContext context) + { + // Try to resolve sessionId directly + var bindingData = context.FunctionContext.BindingContext.BindingData; + bindingData.TryGetValue(Constants.SessionId, out object? sessionId); + + // If sessionId is not found and sessionIdRepeatedFieldArray has a value (isBatched = true), we can just parse the first sessionId from the array, as all the values are guaranteed to be the same. + // This is because there can be multiple messages but each message would belong to the same session. + // Note if web jobs extensions ever adds support for multiple sessions in a single batch, this logic will need to be updated. + if (sessionId is null && bindingData.TryGetValue(Constants.SessionIdArray, out object? sessionIdArray)) + { + var sessionIdRepeatedArray = sessionIdArray as IList; + if (sessionIdRepeatedArray is not null && sessionIdRepeatedArray.Count > 0) + { + sessionId = sessionIdRepeatedArray[0]; // Use the first sessionId in the array + } + } + + if (sessionId is null) + { + throw new InvalidOperationException( + $"Expecting {Constants.SessionId} or {Constants.SessionIdArray} within binding data and value was not present. Sessions must be enabled when binding to {nameof(ServiceBusSessionMessageActions)}."); + } + + return (string)sessionId; + } } } diff --git a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj index 38d7c0075..2b0378c42 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -6,7 +6,7 @@ Azure Service Bus extensions for .NET isolated functions - 5.22.0 + 5.24.0 false @@ -15,17 +15,16 @@ - - - - - + + + + + - @@ -37,7 +36,7 @@ - + diff --git a/extensions/Worker.Extensions.SignalRService/ci/official-build.yml b/extensions/Worker.Extensions.SignalRService/ci/official-build.yml index a98a3131b..7d1a4c541 100644 --- a/extensions/Worker.Extensions.SignalRService/ci/official-build.yml +++ b/extensions/Worker.Extensions.SignalRService/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.SignalRService/ci/official-release.yml b/extensions/Worker.Extensions.SignalRService/ci/official-release.yml new file mode 100644 index 000000000..3917aa1cf --- /dev/null +++ b/extensions/Worker.Extensions.SignalRService/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.signalr.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.SignalRService/ci/public-build.yml b/extensions/Worker.Extensions.SignalRService/ci/public-build.yml index 50784ea6d..fc9421c0c 100644 --- a/extensions/Worker.Extensions.SignalRService/ci/public-build.yml +++ b/extensions/Worker.Extensions.SignalRService/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.SignalRService/release_notes.md b/extensions/Worker.Extensions.SignalRService/release_notes.md index 1f4444583..2d0995b06 100644 --- a/extensions/Worker.Extensions.SignalRService/release_notes.md +++ b/extensions/Worker.Extensions.SignalRService/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.SignalRService 1.15.0 +### Microsoft.Azure.Functions.Worker.Extensions.SignalRService -- Fix SignalR trigger return value not working issue. +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.SignalRService/src/Worker.Extensions.SignalRService.csproj b/extensions/Worker.Extensions.SignalRService/src/Worker.Extensions.SignalRService.csproj index 925fdb45f..b93b76674 100644 --- a/extensions/Worker.Extensions.SignalRService/src/Worker.Extensions.SignalRService.csproj +++ b/extensions/Worker.Extensions.SignalRService/src/Worker.Extensions.SignalRService.csproj @@ -6,7 +6,7 @@ Azure SignalR Service extensions for .NET isolated functions annotations - 1.15.0 + 2.0.1 false @@ -19,15 +19,15 @@ - - - - - + + + + + - + diff --git a/extensions/Worker.Extensions.Storage.Blobs/ci/official-build.yml b/extensions/Worker.Extensions.Storage.Blobs/ci/official-build.yml index 4ba8b334a..6749a6201 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/ci/official-build.yml +++ b/extensions/Worker.Extensions.Storage.Blobs/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Storage.Blobs/ci/official-release.yml b/extensions/Worker.Extensions.Storage.Blobs/ci/official-release.yml new file mode 100644 index 000000000..0190402b8 --- /dev/null +++ b/extensions/Worker.Extensions.Storage.Blobs/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.storage.blobs.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml b/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml index 5332122d4..bde591fa1 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml +++ b/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageConverter.cs b/extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageConverter.cs index 029043908..1498f67c2 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageConverter.cs +++ b/extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageConverter.cs @@ -93,7 +93,7 @@ private BlobBindingData GetBindingDataContent(ModelBindingData bindingData) return bindingData.ContentType switch { - Constants.JsonContentType => bindingData.Content.ToObjectFromJson(), + Constants.JsonContentType => bindingData.Content.ToObjectFromJson()!, _ => throw new InvalidContentTypeException(bindingData.ContentType, Constants.JsonContentType) }; } diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj b/extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj index cd0eb9504..5773fd59e 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj +++ b/extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj @@ -6,7 +6,7 @@ Azure Blob Storage extensions for .NET isolated functions - 6.6.0 + 6.8.0 false @@ -16,13 +16,12 @@ - - - - + + + @@ -30,7 +29,7 @@ - + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Storage.Queues/ci/official-build.yml b/extensions/Worker.Extensions.Storage.Queues/ci/official-build.yml index 8f577d770..419b7c05e 100644 --- a/extensions/Worker.Extensions.Storage.Queues/ci/official-build.yml +++ b/extensions/Worker.Extensions.Storage.Queues/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Storage.Queues/ci/official-release.yml b/extensions/Worker.Extensions.Storage.Queues/ci/official-release.yml new file mode 100644 index 000000000..081c02a58 --- /dev/null +++ b/extensions/Worker.Extensions.Storage.Queues/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.storage.queues.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml b/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml index 9d1b5adac..93dc5ba44 100644 --- a/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml +++ b/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Storage.Queues/src/TypeConverters/QueueMessageConverter.cs b/extensions/Worker.Extensions.Storage.Queues/src/TypeConverters/QueueMessageConverter.cs index 02e319657..fd595c3eb 100644 --- a/extensions/Worker.Extensions.Storage.Queues/src/TypeConverters/QueueMessageConverter.cs +++ b/extensions/Worker.Extensions.Storage.Queues/src/TypeConverters/QueueMessageConverter.cs @@ -38,7 +38,7 @@ private QueueMessage ExtractQueueMessage(ModelBindingData modelBindingData) throw new InvalidContentTypeException(modelBindingData.ContentType, Constants.JsonContentType); } - return modelBindingData.Content.ToObjectFromJson(_jsonOptions); + return modelBindingData.Content.ToObjectFromJson(_jsonOptions)!; } } } diff --git a/extensions/Worker.Extensions.Storage.Queues/src/Worker.Extensions.Storage.Queues.csproj b/extensions/Worker.Extensions.Storage.Queues/src/Worker.Extensions.Storage.Queues.csproj index e70a7b744..8ca17684f 100644 --- a/extensions/Worker.Extensions.Storage.Queues/src/Worker.Extensions.Storage.Queues.csproj +++ b/extensions/Worker.Extensions.Storage.Queues/src/Worker.Extensions.Storage.Queues.csproj @@ -6,7 +6,7 @@ Azure Queue Storage extensions for .NET isolated functions - 5.5.0 + 5.5.3 false @@ -16,11 +16,11 @@ - - + + @@ -28,7 +28,7 @@ - + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Storage/ci/official-build.yml b/extensions/Worker.Extensions.Storage/ci/official-build.yml index 2d9debd6f..0f51f6a3c 100644 --- a/extensions/Worker.Extensions.Storage/ci/official-build.yml +++ b/extensions/Worker.Extensions.Storage/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Storage/ci/official-release.yml b/extensions/Worker.Extensions.Storage/ci/official-release.yml new file mode 100644 index 000000000..00fbb0d9c --- /dev/null +++ b/extensions/Worker.Extensions.Storage/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.storage.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Storage/ci/public-build.yml b/extensions/Worker.Extensions.Storage/ci/public-build.yml index ca4c1c76b..81a8a1319 100644 --- a/extensions/Worker.Extensions.Storage/ci/public-build.yml +++ b/extensions/Worker.Extensions.Storage/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Storage/release_notes.md b/extensions/Worker.Extensions.Storage/release_notes.md index e15e98a87..b47906b12 100644 --- a/extensions/Worker.Extensions.Storage/release_notes.md +++ b/extensions/Worker.Extensions.Storage/release_notes.md @@ -4,15 +4,14 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Storage 6.6.0 - -- Updated `Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs` to 6.6.0 +### Microsoft.Azure.Functions.Worker.Extensions.Storage +- -### Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs 6.6.0 +### Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs -- Updated `Microsoft.Extensions.Azure` dependency to 1.7.4 +- -### Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues +### Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues -- +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj b/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj index 022d03b6e..6fbeccbaa 100644 --- a/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj +++ b/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj @@ -6,7 +6,7 @@ Azure Storage extensions for .NET isolated functions - 6.6.0 + 6.8.0 false diff --git a/extensions/Worker.Extensions.Tables/ci/official-build.yml b/extensions/Worker.Extensions.Tables/ci/official-build.yml index 1de26f1db..e43778db2 100644 --- a/extensions/Worker.Extensions.Tables/ci/official-build.yml +++ b/extensions/Worker.Extensions.Tables/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Tables/ci/official-release.yml b/extensions/Worker.Extensions.Tables/ci/official-release.yml new file mode 100644 index 000000000..3476bd45f --- /dev/null +++ b/extensions/Worker.Extensions.Tables/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.tables.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Tables/ci/public-build.yml b/extensions/Worker.Extensions.Tables/ci/public-build.yml index 624f5998a..1a4fcb639 100644 --- a/extensions/Worker.Extensions.Tables/ci/public-build.yml +++ b/extensions/Worker.Extensions.Tables/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Tables/release_notes.md b/extensions/Worker.Extensions.Tables/release_notes.md index 8350d5fc4..771cce0ca 100644 --- a/extensions/Worker.Extensions.Tables/release_notes.md +++ b/extensions/Worker.Extensions.Tables/release_notes.md @@ -4,7 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Tables 1.4.2 - -- Updated `Microsoft.Extensions.Azure` dependency to 1.7.5 +### Microsoft.Azure.Functions.Worker.Extensions.Tables +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.Tables/src/TableInputAttribute.cs b/extensions/Worker.Extensions.Tables/src/TableInputAttribute.cs index bd873b20e..f182d83be 100644 --- a/extensions/Worker.Extensions.Tables/src/TableInputAttribute.cs +++ b/extensions/Worker.Extensions.Tables/src/TableInputAttribute.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Functions.Worker [InputConverter(typeof(TableClientConverter))] [InputConverter(typeof(TableEntityConverter))] [InputConverter(typeof(TableEntityEnumerableConverter))] + [InputConverter(typeof(TablePocoConverter))] [ConverterFallbackBehavior(ConverterFallbackBehavior.Default)] public class TableInputAttribute : InputBindingAttribute { diff --git a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableClientConverter.cs b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableClientConverter.cs index 86d028fd7..ac0b2d208 100644 --- a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableClientConverter.cs +++ b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableClientConverter.cs @@ -34,6 +34,11 @@ public override ValueTask ConvertAsync(ConverterContext contex return new(ConversionResult.Unhandled()); } + if (context.TargetType != typeof(TableClient)) + { + return new(ConversionResult.Unhandled()); + } + var modelBindingData = context?.Source as ModelBindingData; var tableData = GetBindingDataContent(modelBindingData); var result = ConvertModelBindingData(tableData); @@ -48,10 +53,7 @@ public override ValueTask ConvertAsync(ConverterContext contex private TableClient ConvertModelBindingData(TableData content) { - if (string.IsNullOrEmpty(content.TableName)) - { - throw new ArgumentNullException(nameof(content.TableName)); - } + ThrowIfNullOrEmpty(content.TableName, nameof(content.TableName)); return GetTableClient(content.Connection, content.TableName!); } diff --git a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableConverterBase.cs b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableConverterBase.cs index d4cd6ceed..1f621b46e 100644 --- a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableConverterBase.cs +++ b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableConverterBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Threading.Tasks; using Azure.Data.Tables; using Microsoft.Azure.Functions.Worker.Converters; @@ -30,11 +31,6 @@ protected bool CanConvert(ConverterContext context) throw new ArgumentNullException(nameof(context)); } - if (context.TargetType != typeof(T)) - { - return false; - } - if (context.Source is not ModelBindingData bindingData) { return false; @@ -71,6 +67,52 @@ protected TableClient GetTableClient(string? connection, string tableName) return tableServiceClient.GetTableClient(tableName); } + protected void ThrowIfNullOrEmpty(string? value, string nameOfValue) + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentNullException(nameOfValue); + } + } + + protected async Task> GetEnumerableTableEntityAsync(TableData content) + { + var tableClient = GetTableClient(content.Connection, content.TableName!); + string? filter = content.Filter; + + if (!string.IsNullOrEmpty(content.PartitionKey)) + { + var partitionKeyPredicate = TableClient.CreateQueryFilter($"PartitionKey eq {content.PartitionKey}"); + filter = !string.IsNullOrEmpty(content.Filter) ? $"{partitionKeyPredicate} and {content.Filter}" : partitionKeyPredicate; + } + + int? maxPerPage = null; + if (content.Take > 0) + { + maxPerPage = content.Take; + } + + int countRemaining = content.Take; + + var entities = tableClient.QueryAsync( + filter: filter, + maxPerPage: maxPerPage).ConfigureAwait(false); + + List entityList = new(); + + await foreach (var entity in entities) + { + countRemaining--; + entityList.Add(entity); + if (countRemaining == 0) + { + break; + } + } + + return entityList; + } + protected class TableData { public string? TableName { get; set; } diff --git a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityConverter.cs b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityConverter.cs index b28d53587..cea24a6a6 100644 --- a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityConverter.cs +++ b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityConverter.cs @@ -33,6 +33,10 @@ public override async ValueTask ConvertAsync(ConverterContext { return ConversionResult.Unhandled(); } + if (context.TargetType != typeof(TableEntity)) + { + return ConversionResult.Unhandled(); + } var modelBindingData = context?.Source as ModelBindingData; var tableData = GetBindingDataContent(modelBindingData); @@ -48,20 +52,11 @@ public override async ValueTask ConvertAsync(ConverterContext private async Task ConvertModelBindingData(TableData content) { - if (string.IsNullOrEmpty(content.TableName)) - { - throw new ArgumentNullException(nameof(content.TableName)); - } + ThrowIfNullOrEmpty(content.TableName, nameof(content.TableName)); - if (string.IsNullOrEmpty(content.PartitionKey)) - { - throw new ArgumentNullException(nameof(content.PartitionKey)); - } + ThrowIfNullOrEmpty(content.PartitionKey, nameof(content.PartitionKey)); - if (string.IsNullOrEmpty(content.RowKey)) - { - throw new ArgumentNullException(nameof(content.RowKey)); - } + ThrowIfNullOrEmpty(content.RowKey, nameof(content.RowKey)); return await GetTableEntity(content); } diff --git a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityEnumerableConverter.cs b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityEnumerableConverter.cs index 762a49a72..fc975efea 100644 --- a/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityEnumerableConverter.cs +++ b/extensions/Worker.Extensions.Tables/src/TypeConverters/TableEntityEnumerableConverter.cs @@ -35,6 +35,10 @@ public override async ValueTask ConvertAsync(ConverterContext { return ConversionResult.Unhandled(); } + if (context.TargetType != typeof(IEnumerable)) + { + return ConversionResult.Unhandled(); + } var modelBindingData = context?.Source as ModelBindingData; var tableData = GetBindingDataContent(modelBindingData); @@ -60,45 +64,7 @@ private async Task> ConvertModelBindingData(TableData c throw new InvalidOperationException($"Row key {content.RowKey} cannot have a value if {content.Take} or {content.Filter} are defined"); } - return await GetEnumerableTableEntity(content); - } - - private async Task> GetEnumerableTableEntity(TableData content) - { - var tableClient = GetTableClient(content.Connection, content.TableName!); - string? filter = content.Filter; - - if (!string.IsNullOrEmpty(content.PartitionKey)) - { - var partitionKeyPredicate = TableClient.CreateQueryFilter($"PartitionKey eq {content.PartitionKey}"); - filter = !string.IsNullOrEmpty(content.Filter) ? $"{partitionKeyPredicate} and {content.Filter}" : partitionKeyPredicate; - } - - int? maxPerPage = null; - if (content.Take > 0) - { - maxPerPage = content.Take; - } - - int countRemaining = content.Take; - - var entities = tableClient.QueryAsync( - filter: filter, - maxPerPage: maxPerPage).ConfigureAwait(false); - - List entityList = new(); - - await foreach (var entity in entities) - { - countRemaining--; - entityList.Add(entity); - if (countRemaining == 0) - { - break; - } - } - - return entityList; + return await GetEnumerableTableEntityAsync(content); } } } diff --git a/extensions/Worker.Extensions.Tables/src/TypeConverters/TablePocoConverter.cs b/extensions/Worker.Extensions.Tables/src/TypeConverters/TablePocoConverter.cs new file mode 100644 index 000000000..f2084ff3f --- /dev/null +++ b/extensions/Worker.Extensions.Tables/src/TypeConverters/TablePocoConverter.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Azure.Data.Tables; +using Microsoft.Azure.Functions.Worker.Converters; +using Microsoft.Azure.Functions.Worker.Core; +using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; +using Microsoft.Azure.Functions.Worker.Extensions.Tables.Config; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using System.Threading; +using System.IO; +using System.Text.Json; +using System.Text; +using System.Collections.Generic; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Tables.TypeConverters +{ + /// + /// Converter to bind type parameters. + /// + [SupportsDeferredBinding] + internal sealed class TablePocoConverter : TableConverterBase + { + private readonly IOptions _workerOptions; + + public TablePocoConverter(IOptions workerOptions, IOptionsMonitor tableOptions, ILogger logger) + : base(tableOptions, logger) + { + _workerOptions = workerOptions ?? throw new ArgumentNullException(nameof(workerOptions)); + } + + public override async ValueTask ConvertAsync(ConverterContext context) + { + try + { + if (!CanConvert(context)) + { + return ConversionResult.Unhandled(); + } + + var modelBindingData = context?.Source as ModelBindingData; + var tableData = GetBindingDataContent(modelBindingData); + var result = await ConvertModelBindingDataAsync(tableData, context!.TargetType); + + if (result is null) + { + return ConversionResult.Failed(new InvalidOperationException($"Unable to convert table binding data to type '{context.TargetType.Name}'.")); + } + + return ConversionResult.Success(result); + } + catch (JsonException ex) + { + return ConversionResult.Failed(new InvalidOperationException($"Unsupported binding type: '{context.TargetType}'", ex)); + } + catch (Exception ex) + { + return ConversionResult.Failed(ex); + } + } + + private async Task ConvertModelBindingDataAsync(TableData content, Type targetType) + { + ThrowIfNullOrEmpty(content.TableName, nameof(content.TableName)); + + ThrowIfNullOrEmpty(content.PartitionKey, nameof(content.PartitionKey)); + + var tableClient = GetTableClient(content.Connection, content.TableName!); + + if (targetType.IsCollectionType()) + { + IEnumerable tableEntities = await GetEnumerableTableEntityAsync(content); + return DeserializeToTargetObject(targetType, tableEntities); + } + else + { + ThrowIfNullOrEmpty(content.RowKey, nameof(content.RowKey)); + + TableEntity tableEntity = await tableClient.GetEntityAsync(content.PartitionKey, content.RowKey); + return DeserializeToTargetObject(targetType, tableEntity); + } + } + + private object? DeserializeToTargetObject(Type targetType, object tableEntity) + { + string jsonString = JsonSerializer.Serialize(tableEntity); + byte[] byteArray = Encoding.UTF8.GetBytes(jsonString); + + var stream = new MemoryStream(byteArray); + stream.Seek(0, SeekOrigin.Begin); + + return _workerOptions?.Value?.Serializer?.Deserialize(stream, targetType, CancellationToken.None); + } + } +} diff --git a/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj b/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj index ec390ab98..30c5668e8 100644 --- a/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj +++ b/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj @@ -6,7 +6,7 @@ Azure Table Storage extensions for .NET isolated functions - 1.4.2 + 1.5.0 @@ -14,14 +14,14 @@ - - + - - + + + @@ -29,7 +29,7 @@ - + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Timer/ci/official-build.yml b/extensions/Worker.Extensions.Timer/ci/official-build.yml index 92a81603a..381bea58e 100644 --- a/extensions/Worker.Extensions.Timer/ci/official-build.yml +++ b/extensions/Worker.Extensions.Timer/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Timer/ci/official-release.yml b/extensions/Worker.Extensions.Timer/ci/official-release.yml new file mode 100644 index 000000000..5e9fe690f --- /dev/null +++ b/extensions/Worker.Extensions.Timer/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.timer.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Timer/ci/public-build.yml b/extensions/Worker.Extensions.Timer/ci/public-build.yml index b4176c73b..8dc3e6529 100644 --- a/extensions/Worker.Extensions.Timer/ci/public-build.yml +++ b/extensions/Worker.Extensions.Timer/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/extensions/Worker.Extensions.Timer/src/Worker.Extensions.Timer.csproj b/extensions/Worker.Extensions.Timer/src/Worker.Extensions.Timer.csproj index 45290011c..27a6abf87 100644 --- a/extensions/Worker.Extensions.Timer/src/Worker.Extensions.Timer.csproj +++ b/extensions/Worker.Extensions.Timer/src/Worker.Extensions.Timer.csproj @@ -12,8 +12,11 @@ - + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Warmup/ci/official-build.yml b/extensions/Worker.Extensions.Warmup/ci/official-build.yml index 1dbb3aae3..bc14ce0f2 100644 --- a/extensions/Worker.Extensions.Warmup/ci/official-build.yml +++ b/extensions/Worker.Extensions.Warmup/ci/official-build.yml @@ -35,8 +35,8 @@ extends: parameters: pool: name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc diff --git a/extensions/Worker.Extensions.Warmup/ci/official-release.yml b/extensions/Worker.Extensions.Warmup/ci/official-release.yml new file mode 100644 index 000000000..e7ac837f9 --- /dev/null +++ b/extensions/Worker.Extensions.Warmup/ci/official-release.yml @@ -0,0 +1,18 @@ +parameters: +- name: publishToNugetOrg + displayName: Publish to nuget.org? + type: boolean + default: false + +pr: none +trigger: none + +resources: + pipelines: + - pipeline: build + source: extensions.warmup.official + +extends: + template: /eng/ci/templates/pipelines/release-extension-packages.yml@self + parameters: + publishToNugetOrg: ${{ parameters.publishToNugetOrg }} diff --git a/extensions/Worker.Extensions.Warmup/ci/public-build.yml b/extensions/Worker.Extensions.Warmup/ci/public-build.yml index 8d0db8be9..3db00dffd 100644 --- a/extensions/Worker.Extensions.Warmup/ci/public-build.yml +++ b/extensions/Worker.Extensions.Warmup/ci/public-build.yml @@ -42,8 +42,8 @@ extends: parameters: pool: name: 1es-pool-azfunc-public - image: 1es-ubuntu-22.04 - os: linux + image: 1es-windows-2022 + os: windows sdl: sourceAnalysisPool: name: 1es-pool-azfunc-public diff --git a/global.json b/global.json index 0d655b083..320ad044d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { "sdk": { - "version": "8.0.110", + "version": "10.0.100", + "allowPrerelease": true, "rollForward": "latestFeature" }, "msbuild-sdks": { diff --git a/host/src/FunctionsNetHost/FunctionsNetHost.csproj b/host/src/FunctionsNetHost/FunctionsNetHost.csproj index 9b9495015..f62612d17 100644 --- a/host/src/FunctionsNetHost/FunctionsNetHost.csproj +++ b/host/src/FunctionsNetHost/FunctionsNetHost.csproj @@ -9,6 +9,7 @@ true Speed true + Guard diff --git a/host/src/FunctionsNetHost/global.json b/host/src/FunctionsNetHost/global.json deleted file mode 100644 index 26228fbdf..000000000 --- a/host/src/FunctionsNetHost/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "8.0.110", - "rollForward": "latestMinor" - } -} \ No newline at end of file diff --git a/host/src/PrelaunchApp/App.csproj b/host/src/PrelaunchApp/App.csproj index 1a4cf4c71..796f94048 100644 --- a/host/src/PrelaunchApp/App.csproj +++ b/host/src/PrelaunchApp/App.csproj @@ -1,7 +1,7 @@ Exe - net6.0;net7.0;net8.0 + net6.0;net7.0;net8.0;net9.0 False None diff --git a/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec b/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec index 481a61ada..062477d9b 100644 --- a/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec +++ b/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec @@ -4,7 +4,7 @@ Microsoft.Azure.Functions.DotNetIsolatedNativeHost Microsoft Azure Functions dotnet-isolated native host dotnet-isolated azure-functions azure - 1.0.11 + 1.0.13 Microsoft Microsoft https://github.com/Azure/azure-functions-dotnet-worker diff --git a/release_notes.md b/release_notes.md index 0260c95a6..114ce75d3 100644 --- a/release_notes.md +++ b/release_notes.md @@ -4,18 +4,14 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker (metapackage) 1.23.0 +### Microsoft.Azure.Functions.Worker (metapackage) 2.51.0 -- Updating `Microsoft.Azure.Functions.Worker.Core` to 1.19.0 -- Updating `Microsoft.Azure.Functions.Worker.Grpc` to 1.17.0 -- Updating `Azure.Core` to 1.41.0 +- Updated worker instrumentation to improve telemetry handling. (#3195) -### Microsoft.Azure.Functions.Worker.Core 1.19.0 +### Microsoft.Azure.Functions.Worker.Core 2.51.0 -- Updating `Azure.Core` to 1.41.0 -- Updated service registrations for bootstrapping methods to ensure idempotency. +- Improved telemetry handling, added OpenTelemetry schema compatibility, and streamlined context propagation. (#3195) -### Microsoft.Azure.Functions.Worker.Grpc 1.17.0 +### Microsoft.Azure.Functions.Worker.Grpc 2.51.0 -- Updating `Azure.Core` to 1.41.0 -- Updated service registrations for bootstrapping methods to ensure idempotency. \ No newline at end of file +- Added `Attributes` to FunctionContext.TraceContext. (#3195) diff --git a/samples/AspNetIntegration/AspNetIntegration.csproj b/samples/AspNetIntegration/AspNetIntegration.csproj index 7f91c0385..0fedae833 100644 --- a/samples/AspNetIntegration/AspNetIntegration.csproj +++ b/samples/AspNetIntegration/AspNetIntegration.csproj @@ -8,14 +8,11 @@ - - - - - - - - + + + + + \ No newline at end of file diff --git a/samples/Configuration/HttpFunction.cs b/samples/Configuration/HttpFunction.cs index 5129cee25..4e3d8424f 100644 --- a/samples/Configuration/HttpFunction.cs +++ b/samples/Configuration/HttpFunction.cs @@ -25,15 +25,15 @@ public static MyType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post public class MyType { - public string camelCasePropertyName { get; set; } - public string PascalCasePropertyName { get; set; } - public string NullValue { get; set; } + public string? camelCasePropertyName { get; set; } + public string? PascalCasePropertyName { get; set; } + public string? NullValue { get; set; } // This sample can use both System.Text.Json and Newtonsoft.Json, depending on which // is registered. Including both attributes here. [JsonPropertyName("Changed_Via_System_Text_Json")] [JsonProperty("Changed_Via_Newtonsoft_Json")] - public string DifferentSerializedName { get; set; } + public string? DifferentSerializedName { get; set; } } } } diff --git a/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs b/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs index 003c78905..d16a96191 100644 --- a/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs +++ b/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs @@ -60,7 +60,7 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next } } - private OutputBindingData GetHttpOutputBindingFromMultipleOutputBinding(FunctionContext context) + private static OutputBindingData? GetHttpOutputBindingFromMultipleOutputBinding(FunctionContext context) { // The output binding entry name will be "$return" only when the function return type is HttpResponseData var httpOutputBinding = context.GetOutputBindings() diff --git a/samples/CustomMiddleware/HttpFunction.cs b/samples/CustomMiddleware/HttpFunction.cs index ed7f19949..5d1c9a363 100644 --- a/samples/CustomMiddleware/HttpFunction.cs +++ b/samples/CustomMiddleware/HttpFunction.cs @@ -23,7 +23,7 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g var logger = context.GetLogger(); // Get the item set by the middleware - if (context.Items.TryGetValue("middlewareitem", out object value) && value is string message) + if (context.Items.TryGetValue("middlewareitem", out object? value) && value is string message) { logger.LogInformation("From middleware: {message}", message); } diff --git a/samples/CustomMiddleware/HttpTriggerWithMultipleOutputBindings.cs b/samples/CustomMiddleware/HttpTriggerWithMultipleOutputBindings.cs index ea2e8e4ec..9645d38af 100644 --- a/samples/CustomMiddleware/HttpTriggerWithMultipleOutputBindings.cs +++ b/samples/CustomMiddleware/HttpTriggerWithMultipleOutputBindings.cs @@ -32,8 +32,8 @@ public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get") public class MyOutputType { [QueueOutput("functionstesting2", Connection = "AzureWebJobsStorage")] - public string Name { get; set; } + public string? Name { get; set; } - public HttpResponseData HttpResponse { get; set; } + public HttpResponseData? HttpResponse { get; set; } } } diff --git a/samples/CustomMiddleware/MyCustomMiddleware.cs b/samples/CustomMiddleware/MyCustomMiddleware.cs index 48c61a5bc..ffc5c58e1 100644 --- a/samples/CustomMiddleware/MyCustomMiddleware.cs +++ b/samples/CustomMiddleware/MyCustomMiddleware.cs @@ -20,7 +20,7 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next // This happens after function execution. We can inspect the context after the function // was invoked - if (context.Items.TryGetValue("functionitem", out object value) && value is string message) + if (context.Items.TryGetValue("functionitem", out object? value) && value is string message) { ILogger logger = context.GetLogger(); diff --git a/samples/Extensions/Blob/Book.cs b/samples/Extensions/Blob/Book.cs index 824e88336..383645748 100644 --- a/samples/Extensions/Blob/Book.cs +++ b/samples/Extensions/Blob/Book.cs @@ -5,7 +5,8 @@ namespace SampleApp { public class Book { - public string Id { get; set; } - public string Name { get; set; } + public string? Id { get; set; } + + public string? Name { get; set; } } -} \ No newline at end of file +} diff --git a/samples/Extensions/CosmosDB/CosmosDBFunction.cs b/samples/Extensions/CosmosDB/CosmosDBFunction.cs index d2b044aa2..17a2ec7e0 100644 --- a/samples/Extensions/CosmosDB/CosmosDBFunction.cs +++ b/samples/Extensions/CosmosDB/CosmosDBFunction.cs @@ -21,7 +21,7 @@ public CosmosDBFunction(ILogger logger) [Function(nameof(CosmosDBFunction))] [ExponentialBackoffRetry(5, "00:00:04", "00:15:00")] [CosmosDBOutput("%CosmosDb%", "%CosmosContainerOut%", Connection = "CosmosDBConnection", CreateIfNotExists = true)] - public object Run( + public object? Run( [CosmosDBTrigger( "%CosmosDb%", "%CosmosContainerIn%", @@ -48,9 +48,9 @@ public object Run( public class MyDocument { - public string Id { get; set; } + public string? Id { get; set; } - public string Text { get; set; } + public string? Text { get; set; } public int Number { get; set; } diff --git a/samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs b/samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs index 41d0317a4..62955cd8a 100644 --- a/samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs +++ b/samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs @@ -265,15 +265,15 @@ public void DocByIdFromJSON( public class ToDoItem { - public string Id { get; set; } - public string Description { get; set; } + public string? Id { get; set; } + public string? Description { get; set; } } public class ToDoItemLookup { - public string ToDoItemId { get; set; } + public string? ToDoItemId { get; set; } - public string ToDoItemPartitionKeyValue { get; set; } + public string? ToDoItemPartitionKeyValue { get; set; } } } } diff --git a/samples/Extensions/EventGrid/EventGridFunction.cs b/samples/Extensions/EventGrid/EventGridFunction.cs index aa1b6e004..18b1342ba 100644 --- a/samples/Extensions/EventGrid/EventGridFunction.cs +++ b/samples/Extensions/EventGrid/EventGridFunction.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; @@ -15,8 +13,7 @@ public static class EventGridFunction public static MyEventType Run([EventGridTrigger] MyEventType input, FunctionContext context) { var logger = context.GetLogger(nameof(EventGridFunction)); - - logger.LogInformation(input.Data.ToString()); + logger.LogInformation(input.Data?.ToString()); var outputEvent = new MyEventType() { @@ -34,16 +31,16 @@ public static MyEventType Run([EventGridTrigger] MyEventType input, FunctionCont public class MyEventType { - public string Id { get; set; } + public string? Id { get; set; } - public string Topic { get; set; } + public string? Topic { get; set; } - public string Subject { get; set; } + public string? Subject { get; set; } - public string EventType { get; set; } + public string? EventType { get; set; } public DateTime EventTime { get; set; } - public IDictionary Data { get; set; } + public IDictionary? Data { get; set; } } } diff --git a/samples/Extensions/Extensions.csproj b/samples/Extensions/Extensions.csproj index ccf024250..e93c958ef 100644 --- a/samples/Extensions/Extensions.csproj +++ b/samples/Extensions/Extensions.csproj @@ -9,10 +9,10 @@ - + - + diff --git a/samples/Extensions/MultiOutput/MultiOutput.cs b/samples/Extensions/MultiOutput/MultiOutput.cs index 025ec85ab..e56700a48 100644 --- a/samples/Extensions/MultiOutput/MultiOutput.cs +++ b/samples/Extensions/MultiOutput/MultiOutput.cs @@ -38,7 +38,7 @@ public class MyOutputType [QueueOutput("myQueue")] public string? Name { get; set; } - public HttpResponseData HttpResponse { get; set; } + public HttpResponseData? HttpResponse { get; set; } } // } diff --git a/samples/Extensions/Queue/QueueFunction.cs b/samples/Extensions/Queue/QueueFunction.cs index b9f821c58..5248ce724 100644 --- a/samples/Extensions/Queue/QueueFunction.cs +++ b/samples/Extensions/Queue/QueueFunction.cs @@ -20,15 +20,15 @@ public QueueFunction(ILogger logger) // // - [Function(nameof(QueueFunction))] + [Function(nameof(QueueInputOutputFunction))] [QueueOutput("output-queue")] - public string[] Run([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context) + public string[] QueueInputOutputFunction([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context) // { // Use a string array to return more than one message. string[] messages = { $"Album name = {myQueueItem.Name}", - $"Album songs = {myQueueItem.Songs.ToString()}"}; + $"Album songs = {myQueueItem.Songs}"}; _logger.LogInformation("{msg1},{msg2}", messages[0], messages[1]); @@ -58,10 +58,10 @@ public void QueueBinaryDataFunction([QueueTrigger("input-queue")] BinaryData mes public class Album { - public string Id { get; set; } + public string? Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } - public List Songs { get; set; } + public List? Songs { get; set; } } } diff --git a/samples/Extensions/SignalR/SignalRFunction.cs b/samples/Extensions/SignalR/SignalRFunction.cs index b567e7928..ace3ea3e6 100644 --- a/samples/Extensions/SignalR/SignalRFunction.cs +++ b/samples/Extensions/SignalR/SignalRFunction.cs @@ -33,15 +33,15 @@ public static class SignalRFunction public class MyConnectionInfo { - public string Url { get; set; } + public string? Url { get; set; } - public string AccessToken { get; set; } + public string? AccessToken { get; set; } } public class MyMessage { - public string Target { get; set; } + public string? Target { get; set; } - public object[] Arguments { get; set; } + public object[]? Arguments { get; set; } } } diff --git a/samples/Extensions/Table/TableFunction.cs b/samples/Extensions/Table/TableFunction.cs index 95894ed68..9936538ef 100644 --- a/samples/Extensions/Table/TableFunction.cs +++ b/samples/Extensions/Table/TableFunction.cs @@ -102,10 +102,10 @@ public void TablePocoFunction( public class MyTableData { - public string PartitionKey { get; set; } + public string? PartitionKey { get; set; } - public string RowKey { get; set; } + public string? RowKey { get; set; } - public string Text { get; set; } + public string? Text { get; set; } } } diff --git a/samples/Extensions/Timer/TimerFunction.cs b/samples/Extensions/Timer/TimerFunction.cs index e94913097..1b0d405ca 100644 --- a/samples/Extensions/Timer/TimerFunction.cs +++ b/samples/Extensions/Timer/TimerFunction.cs @@ -15,7 +15,7 @@ public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, FunctionContext context) { var logger = context.GetLogger(nameof(TimerFunction)); - logger.LogInformation($"Function Ran. Next timer schedule = {timerInfo.ScheduleStatus.Next}"); + logger.LogInformation($"Function Ran. Next timer schedule = {timerInfo.ScheduleStatus?.Next}"); } // } diff --git a/samples/FunctionApp/FunctionApp.csproj b/samples/FunctionApp/FunctionApp.csproj index 5423ca69a..1dd3c4ff7 100644 --- a/samples/FunctionApp/FunctionApp.csproj +++ b/samples/FunctionApp/FunctionApp.csproj @@ -8,30 +8,15 @@ - + + + + + + - - - - - - - - - - - - diff --git a/samples/Net7Worker/.vscode/extensions.json b/samples/Net7Worker/.vscode/extensions.json deleted file mode 100644 index dde673dcd..000000000 --- a/samples/Net7Worker/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "ms-azuretools.vscode-azurefunctions" - ] -} \ No newline at end of file diff --git a/samples/Net7Worker/HttpFunction.cs b/samples/Net7Worker/HttpFunction.cs deleted file mode 100644 index 0ffb79e62..000000000 --- a/samples/Net7Worker/HttpFunction.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Net; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; - -namespace Net7Worker -{ - public class HttpFunction - { - private readonly ILogger _logger; - - public HttpFunction(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - [Function(nameof(HttpFunction))] - public HttpResponseData Run( - [HttpTrigger(AuthorizationLevel.Anonymous,"get", "post", Route = null)] HttpRequestData req, - FunctionContext executionContext) - { - _logger.LogInformation("C# HTTP trigger function processing a request."); - - var response = req.CreateResponse(HttpStatusCode.OK); - response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); - response.WriteString("Welcome to Azure Functions - Isolated .NET 7!"); - - return response; - } - } -} diff --git a/samples/Net7Worker/Program.cs b/samples/Net7Worker/Program.cs deleted file mode 100644 index 332b923d1..000000000 --- a/samples/Net7Worker/Program.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Microsoft.Extensions.Hosting; - -var host = new HostBuilder() - .ConfigureFunctionsWorkerDefaults() - .Build(); - -host.Run(); diff --git a/samples/Net7Worker/README.md b/samples/Net7Worker/README.md index b2148c610..2bd6429de 100644 --- a/samples/Net7Worker/README.md +++ b/samples/Net7Worker/README.md @@ -1,12 +1,6 @@ -# .NET Framework Worker +# Former sample for .NET 7 Worker -This sample demonstrates how to create a .NET 7 project using the Isolated model. - -## Function Samples - -### HTTPFunction.cs - -Demonstrates basic HTTP trigger function. +This sample has been removed, as support for .NET 7 ended on May 14, 2024. The `EventHubCancellationToken.cs` file is being temporarily preserved. ### EventHubCancellationToken.cs diff --git a/samples/Net7Worker/local.settings.json b/samples/Net7Worker/local.settings.json deleted file mode 100644 index 9f0c231c0..000000000 --- a/samples/Net7Worker/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "EventHubConnection": "" - } -} \ No newline at end of file diff --git a/samples/Net9FunctionApp/HelloHttp.cs b/samples/Net9FunctionApp/HelloHttp.cs new file mode 100644 index 000000000..6ef279040 --- /dev/null +++ b/samples/Net9FunctionApp/HelloHttp.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace Net9FunctionApp +{ + public class HelloHttp(ILogger logger) + { + [Function("HelloHttp")] + public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req) + { + logger.LogInformation("C# HTTP trigger function processed a request."); + return new OkObjectResult("Welcome to Azure Functions!"); + } + } +} diff --git a/samples/Net7Worker/Net7Worker.csproj b/samples/Net9FunctionApp/Net9FunctionApp.csproj similarity index 55% rename from samples/Net7Worker/Net7Worker.csproj rename to samples/Net9FunctionApp/Net9FunctionApp.csproj index 4834f9247..b6b7bac9e 100644 --- a/samples/Net7Worker/Net7Worker.csproj +++ b/samples/Net9FunctionApp/Net9FunctionApp.csproj @@ -1,16 +1,14 @@  - - net7.0 + net9.0 v4 Exe - - - - - + + + + + - \ No newline at end of file diff --git a/samples/Net9FunctionApp/Program.cs b/samples/Net9FunctionApp/Program.cs new file mode 100644 index 000000000..e0260b707 --- /dev/null +++ b/samples/Net9FunctionApp/Program.cs @@ -0,0 +1,14 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = FunctionsApplication.CreateBuilder(args); +builder.ConfigureFunctionsWebApplication(); + +builder.Services.AddApplicationInsightsTelemetryWorkerService(); +builder.Services.ConfigureFunctionsApplicationInsights(); + +var host = builder.Build(); + +host.Run(); diff --git a/samples/Net7Worker/host.json b/samples/Net9FunctionApp/host.json similarity index 77% rename from samples/Net7Worker/host.json rename to samples/Net9FunctionApp/host.json index beb2e4020..ee5cf5f83 100644 --- a/samples/Net7Worker/host.json +++ b/samples/Net9FunctionApp/host.json @@ -5,7 +5,8 @@ "samplingSettings": { "isEnabled": true, "excludedTypes": "Request" - } + }, + "enableLiveMetricsFilters": true } } } \ No newline at end of file diff --git a/samples/Net9FunctionApp/local.settings.json b/samples/Net9FunctionApp/local.settings.json new file mode 100644 index 000000000..401ae0c34 --- /dev/null +++ b/samples/Net9FunctionApp/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AzureWebJobsStorage": "UseDevelopmentStorage=true" + } +} \ No newline at end of file diff --git a/samples/README.md b/samples/README.md index b339e6650..ff4fc1a07 100644 --- a/samples/README.md +++ b/samples/README.md @@ -6,10 +6,13 @@ This folder contains a set of samples that demonstrate various scenarios. | Sample | Description | | ------ | ------------ | +|[AspNetCore Integration](./AspNetIntegration)| Demonstrates writing `HttpTrigger`s using AspNetCore integration. | |[Configuration](./Configuration)| Demonstrates how to configure the worker using Program.cs and WorkerOptions | |[Custom Middleware](./CustomMiddleware)| Demonstrates how to create and use custom middleware | |[Entity Framework](./EntityFramework)| Demonstrates how to work with entity framework | +|[External Extension Project](./ExternalExtensionProject)| An advanced scenario. Demonstrates supplying the worker extension project manually. | |[Extensions](./Extensions)| Examples of how to work with various extensions | |[Function App](./FunctionApp)| Examples of using the HTTP Trigger; default project for debugging the worker SDK | |[.NET 7 Worker](./Net7Worker)| Demonstrates how to setup a .NET 7 Function App | +|[.NET 9 Worker](./Net9FunctionApp)| Demonstrates how to setup a .NET 9 Function App using `HostApplicationBuilder` | |[.NET Framework Worker](./NetFxWorker)| Demonstrates how to setup a .NET Framework Function App | diff --git a/sdk/FunctionMetadataLoaderExtension/FunctionMetadataLoaderExtension.csproj b/sdk/FunctionMetadataLoaderExtension/FunctionMetadataLoaderExtension.csproj index 69074f8b4..9b4c49c6b 100644 --- a/sdk/FunctionMetadataLoaderExtension/FunctionMetadataLoaderExtension.csproj +++ b/sdk/FunctionMetadataLoaderExtension/FunctionMetadataLoaderExtension.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/sdk/Sdk.Analyzers/Sdk.Analyzers.csproj b/sdk/Sdk.Analyzers/Sdk.Analyzers.csproj index c43f14f4e..bfe34892a 100644 --- a/sdk/Sdk.Analyzers/Sdk.Analyzers.csproj +++ b/sdk/Sdk.Analyzers/Sdk.Analyzers.csproj @@ -2,7 +2,7 @@ 2 - 1 + 2 Library true false @@ -14,22 +14,19 @@ disable Azure Functions Worker, analyzers $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput + true - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - diff --git a/sdk/Sdk.Analyzers/tools/install.ps1 b/sdk/Sdk.Analyzers/tools/install.ps1 deleted file mode 100644 index c1c3d8822..000000000 --- a/sdk/Sdk.Analyzers/tools/install.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not install analyzers via install.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - if (Test-Path $analyzersPath) - { - # Install the language agnostic analyzers. - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Install language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} \ No newline at end of file diff --git a/sdk/Sdk.Analyzers/tools/uninstall.ps1 b/sdk/Sdk.Analyzers/tools/uninstall.ps1 deleted file mode 100644 index 65a862370..000000000 --- a/sdk/Sdk.Analyzers/tools/uninstall.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall the language agnostic analyzers. - if (Test-Path $analyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - try - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - catch - { - - } - } - } - } -} \ No newline at end of file diff --git a/sdk/Sdk.Generators/Constants.cs b/sdk/Sdk.Generators/Constants.cs index 7799f557e..96cd5258a 100644 --- a/sdk/Sdk.Generators/Constants.cs +++ b/sdk/Sdk.Generators/Constants.cs @@ -1,10 +1,22 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; + namespace Microsoft.Azure.Functions.Worker.Sdk.Generators { internal static class Constants { +#pragma warning disable RS1035 // Do not use APIs banned for analyzers + // Suppressing warning based on https://github.com/dotnet/roslyn-analyzers/issues/6467 + internal static readonly string NewLine = Environment.NewLine; +#pragma warning restore RS1035 // Do not use APIs banned for analyzers + + internal static readonly string GeneratedCodeAttribute = + $"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(" + + $"\"{typeof(Constants).Assembly.GetName().Name}\", " + + $"\"{typeof(Constants).Assembly.GetName().Version}\")]"; + internal static class ExecutionModel { internal const string Isolated = "isolated"; diff --git a/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs b/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs index 644faf69c..6cd9dee59 100644 --- a/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs +++ b/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs @@ -102,7 +102,7 @@ internal string GenerateExtensionStartupRunner(GeneratorExecutionContext context namespace {{namespaceValue}} { [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} internal class WorkerExtensionStartupCodeExecutor : global::Microsoft.Azure.Functions.Worker.Core.WorkerExtensionStartup { /// diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs index 40aeee233..199b4f61c 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -33,10 +32,10 @@ internal static string Emit(GeneratorExecutionContext context, IEnumerable _defaultExecutor;" : string.Empty)}} + private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator;{{(defaultExecutorNeeded ? $"{Constants.NewLine} private Lazy _defaultExecutor;" : string.Empty)}} {{GetTypesDictionary(functions)}} public DirectFunctionExecutor(global::Microsoft.Azure.Functions.Worker.IFunctionActivator functionActivator) { @@ -47,12 +46,13 @@ public DirectFunctionExecutor(global::Microsoft.Azure.Functions.Worker.IFunction public async global::System.Threading.Tasks.ValueTask ExecuteAsync(global::Microsoft.Azure.Functions.Worker.FunctionContext context) { {{GetMethodBody(functions, defaultExecutorNeeded)}} - }{{(defaultExecutorNeeded ? $"{Environment.NewLine}{EmitCreateDefaultExecutorMethod(context)}" : string.Empty)}} + }{{(defaultExecutorNeeded ? $"{Constants.NewLine}{EmitCreateDefaultExecutorMethod(context)}" : string.Empty)}} } /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class FunctionExecutorHostBuilderExtensions { /// @@ -105,7 +105,7 @@ private static string GetTypesDictionary(IEnumerable functio return $$""" private readonly Dictionary types = new Dictionary() { - {{string.Join($",{Environment.NewLine} ", typesDict.Select(c => $$""" { "{{c.Key}}", Type.GetType("{{c.Key}}, {{c.Value}}") }"""))}} + {{string.Join($",{Constants.NewLine} ", typesDict.Select(c => $$""" { "{{c.Key}}", Type.GetType("{{c.Key}}, {{c.Value}}") }"""))}} }; """; @@ -121,6 +121,7 @@ private static string GetAutoConfigureStartupClass(bool includeAutoRegistrationC /// Auto startup class to register the custom implementation generated for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {{Constants.GeneratedCodeAttribute}} public class FunctionExecutorAutoStartup : global::Microsoft.Azure.Functions.Worker.IAutoConfigureStartup { /// @@ -147,7 +148,7 @@ private static string GetMethodBody(IEnumerable functions, b var inputBindingFeature = context.Features.Get(); var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context); var inputArguments = inputBindingResult.Values; - {(anyDefaultExecutor ? $" _defaultExecutor = new Lazy(() => CreateDefaultExecutorInstance(context));{Environment.NewLine}" : string.Empty)} + {(anyDefaultExecutor ? $" _defaultExecutor = new Lazy(() => CreateDefaultExecutorInstance(context));{Constants.NewLine}" : string.Empty)} """); foreach (ExecutableFunction function in functions) @@ -157,7 +158,7 @@ private static string GetMethodBody(IEnumerable functions, b if (string.Equals(context.FunctionDefinition.EntryPoint, "{{function.EntryPoint}}", StringComparison.Ordinal)) { - {{(fast ? EmitFastPath(function) : EmitSlowPath())}} + {{(fast ? EmitFastPath(function) : EmitSlowPath())}} return; } """); @@ -169,21 +170,22 @@ private static string GetMethodBody(IEnumerable functions, b private static string EmitFastPath(ExecutableFunction function) { var sb = new StringBuilder(); - + if (!function.IsStatic) { sb.Append($""" - var instanceType = types["{function.ParentFunctionClassName}"]; + var instanceType = types["{function.ParentFunctionClassName}"]; var i = _functionActivator.CreateInstance(instanceType, context) as {function.ParentFunctionFullyQualifiedClassName}; + """); } - sb.Append(!function.IsStatic - ? """ - - - """ - : " "); + if (function.IsObsolete) + { + sb.AppendLine("#pragma warning disable CS0618"); + } + + sb.Append(" "); if (function.IsReturnValueAssignable) { @@ -209,13 +211,19 @@ private static string EmitFastPath(ExecutableFunction function) sb.Append(function.IsStatic ? $"{function.ParentFunctionFullyQualifiedClassName}.{function.MethodName}({methodParamsStr});" : $"i.{function.MethodName}({methodParamsStr});"); + + if (function.IsObsolete) + { + sb.Append(Constants.NewLine); + sb.Append("#pragma warning restore CS0618"); + } return sb.ToString(); } private static string EmitSlowPath() { - return " await _defaultExecutor.Value.ExecuteAsync(context);"; + return " await _defaultExecutor.Value.ExecuteAsync(context);"; } } } diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs index 2dbcfe7b6..b9821b305 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.ExecutableFunction.cs @@ -63,5 +63,10 @@ internal class ExecutableFunction /// ex: FooAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=9475d07f10cb09df /// internal string AssemblyIdentity { get; set; } = null!; + + /// + /// Gets or sets if the function is Obsolete. + /// + internal bool IsObsolete { get; set; } } } diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs index fd6c570fc..c4b0b747f 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs @@ -42,6 +42,9 @@ internal ICollection GetFunctions(IEnumerable var defaultFormatClassName = method.ContainingSymbol.ToDisplayString(); var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var isObsolete = method.GetAttributes() + .Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, _knownTypes.ObsoleteAttr)); + var function = new ExecutableFunction { EntryPoint = $"{defaultFormatClassName}.{method.Name}", @@ -54,6 +57,7 @@ internal ICollection GetFunctions(IEnumerable ParentFunctionFullyQualifiedClassName = fullyQualifiedClassName, Visibility = method.GetVisibility(), AssemblyIdentity = method.ContainingAssembly.Identity.GetDisplayName(), + IsObsolete = isObsolete, }; functionList.Add(function); diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.CardinalityParser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.CardinalityParser.cs index a86a856ba..094ee8f60 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.CardinalityParser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.CardinalityParser.cs @@ -176,7 +176,7 @@ private DataType ResolveIEnumerableOfT(IParameterSymbol parameterSymbol, out boo break; } - genericInterfaceSymbol = currSymbol.Interfaces.Where(i => i.IsOrDerivedFrom(_knownTypes.IEnumerableGeneric)).FirstOrDefault(); + genericInterfaceSymbol = currSymbol.Interfaces.FirstOrDefault(i => i.IsOrDerivedFrom(_knownTypes.IEnumerableGeneric)); if (genericInterfaceSymbol != null) { finalSymbol = genericInterfaceSymbol; diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs index 0fa52199e..fdbc7e283 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System.CodeDom.Compiler; using System.Collections.Generic; using System.Text; using System.Text.Json; @@ -38,10 +39,10 @@ public static string Emit(GeneratorExecutionContext context, IReadOnlyList - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -56,6 +57,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -85,6 +87,7 @@ private static string GetAutoConfigureStartupClass(bool includeAutoRegistrationC /// Auto startup class to register the custom implementation generated for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {{Constants.GeneratedCodeAttribute}} public class FunctionMetadataProviderAutoStartup : global::Microsoft.Azure.Functions.Worker.IAutoConfigureStartup { /// diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index 7acafdcaf..776c555d0 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information.; using System; @@ -612,7 +612,7 @@ private bool TryCreateBindingDictionary(AttributeData bindingAttrData, string bi private bool TryGetAttributeProperties(AttributeData attributeData, Location? attribLocation, out IDictionary? attrProperties) { - attrProperties = new Dictionary(); + attrProperties = new Dictionary(StringComparer.OrdinalIgnoreCase); if (attributeData.ConstructorArguments.Any()) { diff --git a/sdk/Sdk.Generators/FunctionMethodSyntaxReceiver.cs b/sdk/Sdk.Generators/FunctionMethodSyntaxReceiver.cs index f5487bf2b..880927beb 100644 --- a/sdk/Sdk.Generators/FunctionMethodSyntaxReceiver.cs +++ b/sdk/Sdk.Generators/FunctionMethodSyntaxReceiver.cs @@ -21,12 +21,10 @@ internal class FunctionMethodSyntaxReceiver : ISyntaxReceiver /// public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { - if (syntaxNode is MethodDeclarationSyntax methodSyntax) + if (syntaxNode is MethodDeclarationSyntax {AttributeLists.Count: > 0} methodSyntax) { - if (methodSyntax.AttributeLists.Count > 0) // collect all methods with attributes - we will verify they are functions when we have access to symbols to get the full name - { - CandidateMethods.Add(methodSyntax); - } + // collect all methods with attributes - we will verify they are functions when we have access to symbols to get the full name + CandidateMethods.Add(methodSyntax); } } } diff --git a/sdk/Sdk.Generators/KnownTypes.cs b/sdk/Sdk.Generators/KnownTypes.cs index fc32225cc..81321ef38 100644 --- a/sdk/Sdk.Generators/KnownTypes.cs +++ b/sdk/Sdk.Generators/KnownTypes.cs @@ -27,6 +27,7 @@ internal readonly struct KnownTypes private readonly Lazy _readOnlyMemoryOfBytes; private readonly Lazy _lookupGeneric; private readonly Lazy _dictionaryGeneric; + private readonly Lazy _obsoleteAttr; internal KnownTypes(Compilation compilation) { @@ -44,6 +45,7 @@ internal KnownTypes(Compilation compilation) _readOnlyMemoryOfBytes = new Lazy(() => compilation.GetTypeByMetadataName(typeof(ReadOnlyMemory).FullName)!); _lookupGeneric = new Lazy(() => compilation.GetTypeByMetadataName(typeof(ILookup<,>).FullName)!); _dictionaryGeneric = new Lazy(() => compilation.GetTypeByMetadataName(typeof(IDictionary<,>).FullName)!); + _obsoleteAttr = new Lazy(() => compilation.GetTypeByMetadataName(typeof(ObsoleteAttribute).FullName)!); } public INamedTypeSymbol TaskType { get => _taskType.Value; } @@ -73,5 +75,7 @@ internal KnownTypes(Compilation compilation) public INamedTypeSymbol LookupGeneric { get => _lookupGeneric.Value; } public INamedTypeSymbol DictionaryGeneric { get => _dictionaryGeneric.Value; } + + public INamedTypeSymbol ObsoleteAttr { get => _obsoleteAttr.Value; } } } diff --git a/sdk/Sdk.Generators/Properties/AssemblyInfo.cs b/sdk/Sdk.Generators/Properties/AssemblyInfo.cs index 7997f2a1d..1c68e8efb 100644 --- a/sdk/Sdk.Generators/Properties/AssemblyInfo.cs +++ b/sdk/Sdk.Generators/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.SdkGeneratorTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Sdk.Generator.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/sdk/Sdk.Generators/Sdk.Generators.csproj b/sdk/Sdk.Generators/Sdk.Generators.csproj index f673aa8e9..159fe0374 100644 --- a/sdk/Sdk.Generators/Sdk.Generators.csproj +++ b/sdk/Sdk.Generators/Sdk.Generators.csproj @@ -10,21 +10,22 @@ false true 3 - 4 + 6 true + true - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -35,4 +36,8 @@ + + + + diff --git a/sdk/Sdk/Constants.cs b/sdk/Sdk/Constants.cs index 91573e53d..4a95ad3fd 100644 --- a/sdk/Sdk/Constants.cs +++ b/sdk/Sdk/Constants.cs @@ -43,10 +43,10 @@ internal static class Constants // NetFramework internal const string NetCoreApp31 = "netcoreapp3.1"; - internal const string Net60 = "net6.0"; + internal const string Net80 = "net8.0"; internal const string NetCoreApp = ".NETCoreApp"; - internal const string NetCoreVersion6 = "v6.0"; internal const string NetCoreVersion31 = "v3.1"; + internal const string NetCoreVersion8 = "v8.0"; internal const string AzureFunctionsVersion3 = "v3"; // Binding directions diff --git a/sdk/Sdk/Extensions/StringExtensions.cs b/sdk/Sdk/Extensions/StringExtensions.cs index d91bbb429..efbd43b24 100644 --- a/sdk/Sdk/Extensions/StringExtensions.cs +++ b/sdk/Sdk/Extensions/StringExtensions.cs @@ -1,8 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Linq; using System; +using System.Linq; namespace Microsoft.Azure.Functions.Worker.Sdk { @@ -11,11 +11,11 @@ internal static class StringExtensions /// /// Returns a copy of the string where the first character is in lower case. /// - public static string ToLowerFirstCharacter(this string str) + public static string? ToLowerFirstCharacter(this string? str) { if (!string.IsNullOrEmpty(str)) { - return Char.ToLowerInvariant(str.First()) + str.Substring(1); + return Char.ToLowerInvariant(str.First()) + str!.Substring(1); } else { diff --git a/sdk/Sdk/ExtensionsCsprojGenerator.cs b/sdk/Sdk/ExtensionsCsprojGenerator.cs index b8ba95584..52b5b133c 100644 --- a/sdk/Sdk/ExtensionsCsprojGenerator.cs +++ b/sdk/Sdk/ExtensionsCsprojGenerator.cs @@ -10,8 +10,6 @@ namespace Microsoft.Azure.Functions.Worker.Sdk { internal class ExtensionsCsprojGenerator { - internal const string ExtensionsProjectName = "WorkerExtensions.csproj"; - private readonly IDictionary _extensions; private readonly string _outputPath; private readonly string _targetFrameworkIdentifier; @@ -29,38 +27,26 @@ public ExtensionsCsprojGenerator(IDictionary extensions, string public void Generate() { - var extensionsCsprojFilePath = Path.Combine(_outputPath, ExtensionsProjectName); - string csproj = GetCsProjContent(); - if (File.Exists(extensionsCsprojFilePath)) + if (File.Exists(_outputPath)) { - string existing = File.ReadAllText(extensionsCsprojFilePath); + string existing = File.ReadAllText(_outputPath); if (string.Equals(csproj, existing, StringComparison.Ordinal)) { // If contents are the same, only touch the file to update timestamp. - File.SetLastWriteTimeUtc(extensionsCsprojFilePath, DateTime.UtcNow); + File.SetLastWriteTimeUtc(_outputPath, DateTime.UtcNow); return; } } - RecreateDirectory(_outputPath); - File.WriteAllText(extensionsCsprojFilePath, csproj); - } - - private void RecreateDirectory(string directoryPath) - { - if (Directory.Exists(directoryPath)) - { - Directory.Delete(directoryPath, recursive: true); - } - - Directory.CreateDirectory(directoryPath); + Directory.CreateDirectory(Path.GetDirectoryName(_outputPath)); + File.WriteAllText(_outputPath, csproj); } internal string GetCsProjContent() { string extensionReferences = GetExtensionReferences(); - string targetFramework = Constants.Net60; + string targetFramework = Constants.Net80; if (_targetFrameworkIdentifier.Equals(Constants.NetCoreApp, StringComparison.OrdinalIgnoreCase)) { @@ -70,16 +56,14 @@ internal string GetCsProjContent() } } - string netSdkVersion = _azureFunctionsVersion.StartsWith(Constants.AzureFunctionsVersion3, StringComparison.OrdinalIgnoreCase) ? "3.1.2" : "4.3.0"; + string netSdkVersion = _azureFunctionsVersion.StartsWith(Constants.AzureFunctionsVersion3, StringComparison.OrdinalIgnoreCase) ? "3.1.2" : "4.6.0"; return $@" {targetFramework} - Release Microsoft.Azure.Functions.Worker.Extensions true - false diff --git a/sdk/Sdk/ExtensionsMetadata.cs b/sdk/Sdk/ExtensionsMetadata.cs index f7ed7912f..2fcd1ae18 100644 --- a/sdk/Sdk/ExtensionsMetadata.cs +++ b/sdk/Sdk/ExtensionsMetadata.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; @@ -9,6 +9,6 @@ namespace Microsoft.Azure.Functions.Worker.Sdk public class ExtensionsMetadata { [JsonPropertyName("extensions")] - public IEnumerable? Extensions { get; set; } + public List Extensions { get; set; } = new List(); } } diff --git a/sdk/Sdk/ExtensionsMetadataEnhancer.cs b/sdk/Sdk/ExtensionsMetadataEnhancer.cs index 9259dbc70..bbb2671aa 100644 --- a/sdk/Sdk/ExtensionsMetadataEnhancer.cs +++ b/sdk/Sdk/ExtensionsMetadataEnhancer.cs @@ -2,7 +2,11 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; +using Mono.Cecil; namespace Microsoft.Azure.Functions.Worker.Sdk { @@ -17,7 +21,6 @@ public static void AddHintPath(IEnumerable extensions) { string? assemblyName = GetAssemblyNameOrNull(extension.TypeName); - // TODO: Worth checking if assembly if also present in there? if (string.IsNullOrEmpty(extension.HintPath) && !string.IsNullOrEmpty(assemblyName)) { extension.HintPath = $@"{ExtensionsBinaryDirectoryPath}/{assemblyName}.dll"; @@ -25,6 +28,34 @@ public static void AddHintPath(IEnumerable extensions) } } + public static IEnumerable GetWebJobsExtensions(string fileName) + { + // NOTE: this is an incomplete approach to getting extensions and is intended only for our usages. + // Running this with arbitrary assemblies (especially user supplied) can lead to exceptions. + AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(fileName); + IEnumerable attributes = assembly.Modules.SelectMany(p => p.GetCustomAttributes()) + .Where(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Hosting.WebJobsStartupAttribute"); + + foreach (CustomAttribute attribute in attributes) + { + CustomAttributeArgument typeProperty = attribute.ConstructorArguments.ElementAtOrDefault(0); + CustomAttributeArgument nameProperty = attribute.ConstructorArguments.ElementAtOrDefault(1); + + TypeDefinition typeDef = (TypeDefinition)typeProperty.Value; + string assemblyQualifiedName = Assembly.CreateQualifiedName( + typeDef.Module.Assembly.FullName, GetReflectionFullName(typeDef)); + + string name = GetName((string)nameProperty.Value, typeDef); + + yield return new ExtensionReference + { + Name = name, + TypeName = assemblyQualifiedName, + HintPath = $@"{ExtensionsBinaryDirectoryPath}/{Path.GetFileName(fileName)}", + }; + } + } + private static string? GetAssemblyNameOrNull(string? typeName) { if (typeName == null) @@ -34,12 +65,40 @@ public static void AddHintPath(IEnumerable extensions) var match = Regex.Match(typeName, AssemblyNameFromQualifiedNameRegex); - if (match.Success && match.Groups.Count == 2) + if (match is {Success: true, Groups.Count: 2}) { return match.Groups[1].Value; } return null; } + + // Copying the WebJobsStartup constructor logic from: + // https://github.com/Azure/azure-webjobs-sdk/blob/e5417775bcb8c8d3d53698932ca8e4e265eac66d/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsStartupAttribute.cs#L33-L47. + private static string GetName(string name, TypeDefinition startupTypeDef) + { + if (string.IsNullOrEmpty(name)) + { + // for a startup class named 'CustomConfigWebJobsStartup' or 'CustomConfigStartup', + // default to a name 'CustomConfig' + name = startupTypeDef.Name; + int idx = name.IndexOf("WebJobsStartup"); + if (idx < 0) + { + idx = name.IndexOf("Startup"); + } + if (idx > 0) + { + name = name.Substring(0, idx); + } + } + + return name; + } + + private static string GetReflectionFullName(TypeReference typeRef) + { + return typeRef.FullName.Replace("/", "+"); + } } } diff --git a/sdk/Sdk/FunctionMetadataGenerator.cs b/sdk/Sdk/FunctionMetadataGenerator.cs index 38c9693ea..bad72cf7b 100644 --- a/sdk/Sdk/FunctionMetadataGenerator.cs +++ b/sdk/Sdk/FunctionMetadataGenerator.cs @@ -741,7 +741,7 @@ private static string GetBindingType(CustomAttribute attribute) // The first character of "Type" property value must be lower case for the scaling infrastructure to work correctly bindingType = bindingType.ToLowerFirstCharacter(); - return bindingType; + return bindingType!; } private static void AddHttpOutputBinding(IList bindingMetadata, string name) diff --git a/sdk/Sdk/FunctionMetadataJsonWriter.cs b/sdk/Sdk/FunctionMetadataJsonWriter.cs index 94e4347f8..b9706732d 100644 --- a/sdk/Sdk/FunctionMetadataJsonWriter.cs +++ b/sdk/Sdk/FunctionMetadataJsonWriter.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.IO; using System.Text.Json; @@ -19,7 +20,7 @@ private static JsonSerializerOptions CreateSerializerOptions() return new JsonSerializerOptions { WriteIndented = true, - IgnoreNullValues = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true, IgnoreReadOnlyProperties = true, DictionaryKeyPolicy = namingPolicy, @@ -34,9 +35,26 @@ private static JsonSerializerOptions CreateSerializerOptions() public static void WriteMetadata(IEnumerable functions, string metadataFileDirectory) { string metadataFile = Path.Combine(metadataFileDirectory, FileName); - using var fs = new FileStream(metadataFile, FileMode.Create, FileAccess.Write); - using var writer = new Utf8JsonWriter(fs, new JsonWriterOptions { Indented = true }); - JsonSerializer.Serialize(writer, functions, s_serializerOptions); + string newContent = JsonSerializer.Serialize(functions, s_serializerOptions); + if (TryReadFile(metadataFile, out string? current) && string.Equals(current, newContent, StringComparison.Ordinal)) + { + // Incremental build support. Skip writing if the content is the same. + return; + } + + File.WriteAllText(metadataFile, newContent); + } + + private static bool TryReadFile(string filePath, out string? content) + { + if (File.Exists(filePath)) + { + content = File.ReadAllText(filePath); + return true; + } + + content = null; + return false; } } } diff --git a/sdk/Sdk/Properties/AssemblyInfo.cs b/sdk/Sdk/Properties/AssemblyInfo.cs index 7bd7d7957..b9e27da61 100644 --- a/sdk/Sdk/Properties/AssemblyInfo.cs +++ b/sdk/Sdk/Properties/AssemblyInfo.cs @@ -3,6 +3,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.SdkTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] -[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.SdkE2ETests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Sdk.Generator.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Sdk.E2ETests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/sdk/Sdk/Sdk.csproj b/sdk/Sdk/Sdk.csproj index 1cc232248..1da879421 100644 --- a/sdk/Sdk/Sdk.csproj +++ b/sdk/Sdk/Sdk.csproj @@ -1,8 +1,9 @@  - 18 - 1 + 2 + 0 + 7 netstandard2.0;net472 Microsoft.Azure.Functions.Worker.Sdk This package provides development time support for the Azure Functions .NET Worker. @@ -30,10 +31,10 @@ - - - - + + + + diff --git a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.Publish.targets b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.Publish.targets index 2e128f9fe..2a72f6f01 100644 --- a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.Publish.targets +++ b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.Publish.targets @@ -21,4 +21,30 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and + + + <_FunctionsRuntimeMajorVersion>$(_AzureFunctionsVersionStandardized.TrimStart('vV')) + mcr.microsoft.com/azure-functions/dotnet-isolated:$(_FunctionsRuntimeMajorVersion)-dotnet-isolated$(TargetFrameworkVersion.TrimStart('vV')) + /home/site/wwwroot + + linux-x64 + + + + + + + + + + \ No newline at end of file diff --git a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets index a5389f5c7..dfc2c085d 100644 --- a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets +++ b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.targets @@ -10,30 +10,40 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and --> + + + <_FunctionsTaskFramework Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_FunctionsTaskFramework Condition="'$(_FunctionsTaskFramework)' == ''">net472 + <_FunctionsTasksDir Condition="'$(_FunctionsTasksDir)'==''">$(MSBuildThisFileDirectory)..\tools\$(_FunctionsTaskFramework)\ + <_FunctionsTaskAssemblyFullPath Condition=" '$(_FunctionsTaskAssemblyFullPath)'=='' ">$(_FunctionsTasksDir)\Microsoft.Azure.Functions.Worker.Sdk.dll + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed.Functions\ + + + + + <_FunctionsExtensionsDirectory>.azurefunctions + <_FunctionsExtensionsJsonName>extensions.json + <_FunctionsWorkerConfigInputFile>$([MSBuild]::NormalizePath($(MSBuildThisFileDirectory)\..\tools\worker.config.json)) + <_FunctionsMetadataLoaderExtensionFile>$([MSBuild]::NormalizePath($(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll)) + <_FunctionsExtensionCommonProps>ImportDirectoryBuildProps=false;ImportDirectoryBuildTargets=false;ImportDirectoryPackagesProps=false + <_FunctionsExtensionRemoveProps>TargetFramework;RuntimeIdentifier;SelfContained;PublishSingleFile;PublishReadyToRun;UseCurrentRuntimeIdentifier;WebPublishMethod;PublishProfile;DeployOnBuild;PublishDir;OutDir;OutputPath; + + + - <_ToolingSuffix> <_DefaultAzureFunctionsVersion>v4 <_AzureFunctionsVersionNotSet Condition="'$(AzureFunctionsVersion)' == ''">true $(_DefaultAzureFunctionsVersion) + true + <_ToolingSuffix Condition="($(AzureFunctionsVersion.StartsWith('v3',StringComparison.OrdinalIgnoreCase)) Or $(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase))) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v5.0'">net5-isolated <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v6.0'">net6-isolated <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v7.0'">net7-isolated <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v8.0'">net8-isolated + <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v9.0'">net9-isolated + <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(TargetFrameworkVersion)' == 'v10.0'">net10-isolated <_ToolingSuffix Condition="$(AzureFunctionsVersion.StartsWith('v4',StringComparison.OrdinalIgnoreCase)) And '$(TargetFrameworkIdentifier)' == '.NETFramework'">netfx-isolated $(_ToolingSuffix) - <_FunctionsTaskFramework Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 - <_FunctionsTaskFramework Condition="'$(_FunctionsTaskFramework)' == ''">net472 - <_FunctionsTasksDir Condition="'$(_FunctionsTasksDir)'==''">$(MSBuildThisFileDirectory)..\tools\$(_FunctionsTaskFramework)\ - <_FunctionsTaskAssemblyFullPath Condition=" '$(_FunctionsTaskAssemblyFullPath)'=='' ">$(_FunctionsTasksDir)\Microsoft.Azure.Functions.Worker.Sdk.dll - - <_FunctionsExtensionCommonProps>ImportDirectoryBuildProps=false;ImportDirectoryBuildTargets=false;ImportDirectoryPackagesProps=false - <_FunctionsExtensionRemoveProps>TargetFramework;Platform;RuntimeIdentifier;SelfContained;PublishSingleFile;PublishReadyToRun;UseCurrentRuntimeIdentifier;WebPublishMethod;PublishProfile;DeployOnBuild;PublishDir - <_FunctionsWorkerConfigInputFile>$(MSBuildThisFileDirectory)\..\tools\worker.config.json - - <_FunctionsMetadataLoaderExtensionFile>$(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll - <_FunctionsExtensionsDirectory>.azurefunctions - <_FunctionsExtensionsJsonName>extensions.json - $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed.Functions\ false true @@ -43,13 +53,14 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and true true - <_FunctionsGenerateExtensionProject Condition="'$(DesignTimeBuild)' != 'true'">true + <_FunctionsBuildEnabled>true + <_FunctionsBuildEnabled Condition="'$(DesignTimeBuild)' == 'true'">false $(RootNamespace.Replace("-", "_")) <_FunctionsVersion Include="v3" InSupport="false" /> - <_FunctionsVersion Include="$(_DefaultAzureFunctionsVersion)" InSupport="true" /> + <_FunctionsVersion Include="v4" InSupport="true" /> @@ -60,12 +71,26 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and + + + $([System.IO.Path]::GetDirectoryName($(ExtensionsCsProj))) + $(IntermediateOutputPath)WorkerExtensions + $([System.IO.Path]::GetFullPath($(ExtensionsCsProjDirectory))) + $([System.IO.Path]::Combine($(ExtensionsCsProjDirectory), WorkerExtensions.csproj)) + + + + <_FunctionsMetadataPath>$(IntermediateOutputPath)functions.metadata + <_FunctionsWorkerConfigPath>$(IntermediateOutputPath)worker.config.json + + + - + <_AzureFunctionsVersionStandardized>$(AzureFunctionsVersion.ToLowerInvariant().Split('-')[0]) - true + <_FunctionsExtensionRemoveProps>$(_FunctionsExtensionRemoveProps)ManagePackageVersionsCentrally; @@ -73,75 +98,104 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and + - - + + + + + + + cmd + /C func start $(RunArguments) + $(OutDir) + + + + + func + start $(RunArguments) + $(OutDir) + + + + + + + + + true + + + + + + + + + DependsOnTargets="_WorkerExtensionsRestore;_WorkerExtensionsBuild" /> - + - - + + + + - - + + + - <_FunctionsMetadataPath>$(IntermediateOutputPath)functions.metadata - <_FunctionsWorkerConfigPath>$(IntermediateOutputPath)worker.config.json - $(IntermediateOutputPath)WorkerExtensions - $([System.IO.Path]::GetFullPath($(ExtensionsCsProjDirectory))) - $([System.IO.Path]::Combine($(ExtensionsCsProjDirectory), WorkerExtensions.csproj)) - <_FunctionsIntermediateExtensionJsonPath>$(ExtensionsCsProjDirectory)\buildout\bin\$(_FunctionsExtensionsJsonName) + <_WorkerExtensionTarget>Build + <_WorkerExtensionTarget Condition="'$(NoBuild)' == 'true'">GetTargetPath + <_WorkerExtensionProperties>Configuration=Release;$(_FunctionsExtensionCommonProps) + + + + + + + + <_FunctionsExtensionsOutputPath>$([System.IO.Path]::GetDirectoryName($(_WorkerExtensionsAssembly))) + <_FunctionsIntermediateExtensionJsonPath>$(_FunctionsExtensionsOutputPath)/bin/$(_FunctionsExtensionsJsonName) <_FunctionsIntermediateExtensionUpdatedJsonPath>$(IntermediateOutputPath)$(_FunctionsExtensionsJsonName) - - - - - - - - + + - + - + - - - - - @@ -158,30 +212,16 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and .Replace('$functionExe$', '$(_FunctionsExecutable)') .Replace('$functionWorker$', '$(TargetName)$(TargetExt)') .Replace('$enableWorkerIndexing$', '$(FunctionsEnableWorkerIndexing)'))" - Overwrite="true" /> - - - - - - - - - - + Overwrite="true" + WriteOnlyWhenDifferent="true" /> - + + OutputPath="$(_FunctionsIntermediateExtensionUpdatedJsonPath)" + AdditionalExtensions="$(_FunctionsMetadataLoaderExtensionFile)" /> - <_ExtensionBinaries Include="$(ExtensionsCsProjDirectory)\buildout\bin\**" - Exclude="$(ExtensionsCsProjDirectory)\buildout\bin\runtimes\**;$(_FunctionsIntermediateExtensionJsonPath)" - CopyToOutputDirectory="PreserveNewest" - CopyToPublishDirectory="PreserveNewest" /> - <_ExtensionRuntimeBinaries Include="$(ExtensionsCsProjDirectory)\buildout\runtimes\**" - CopyToOutputDirectory="PreserveNewest" - CopyToPublishDirectory="PreserveNewest" /> + <_ExtensionBinaries Include="$(_FunctionsExtensionsOutputPath)\bin\**" + Exclude="$(_FunctionsExtensionsOutputPath)\bin\runtimes\**;$(_FunctionsIntermediateExtensionJsonPath)" /> + <_ExtensionRuntimeBinaries Include="$(_FunctionsExtensionsOutputPath)\runtimes\**" /> - + - + - <_NoneWithTargetPath Include="@(_ExtensionFilesWithTargetPath)" TargetPath="$(_FunctionsExtensionsDirectory)/%(_ExtensionFilesWithTargetPath.TargetPath)" /> + <_FunctionsAdditionalFile Include="$(_FunctionsMetadataPath);$(_FunctionsWorkerConfigPath);$(_FunctionsIntermediateExtensionUpdatedJsonPath)" /> + <_FunctionsAdditionalFile Include="$(_FunctionsMetadataLoaderExtensionFile)" SubPath="$(_FunctionsExtensionsDirectory)/" /> + <_NoneWithTargetPath Include="@(_FunctionsAdditionalFile)" + TargetPath="%(_FunctionsAdditionalFile.SubPath)%(Filename)%(Extension)" + CopyToOutputDirectory="PreserveNewest" + CopyToPublishDirectory="PreserveNewest"/> + <_NoneWithTargetPath Include="@(_ExtensionFilesWithTargetPath)" + TargetPath="$(_FunctionsExtensionsDirectory)/%(_ExtensionFilesWithTargetPath.TargetPath)" + CopyToOutputDirectory="PreserveNewest" + CopyToPublishDirectory="PreserveNewest"/> - <_WorkerExtFilesToClean Include="$(ExtensionsCsProjDirectory)\**" Condition="'$(ExtensionsCsProjDirectory)' != ''" /> - <_WorkerExtFilesToClean Include="$(TargetDir)$(_FunctionsExtensionsDirectory)\**" /> + <_WorkerExtFilesToClean Include="$(ExtensionsCsProjDirectory)/**" Condition="'$(ExtensionsCsProjDirectory)' != ''" /> + <_WorkerExtFilesToClean Include="$(TargetDir)$(_FunctionsExtensionsDirectory)/**" /> <_WorkerExtFilesToClean Include="$(_FunctionsMetadataPath)" /> <_WorkerExtFilesToClean Include="$(_FunctionsWorkerConfigPath)" /> <_WorkerExtFilesToClean Include="$(TargetDir)worker.config.json" /> @@ -235,4 +280,4 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - \ No newline at end of file + diff --git a/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs b/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs index c76bb4871..4d0460761 100644 --- a/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs +++ b/sdk/Sdk/Tasks/EnhanceExtensionsMetadata.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; @@ -24,12 +25,19 @@ public class EnhanceExtensionsMetadata : Task [Required] public string? OutputPath { get; set; } + public ITaskItem[]? AdditionalExtensions { get; set; } + public override bool Execute() { string json = File.ReadAllText(ExtensionsJsonPath); - var extensionsMetadata = JsonSerializer.Deserialize(json); - ExtensionsMetadataEnhancer.AddHintPath(extensionsMetadata?.Extensions ?? Enumerable.Empty()); + var extensionsMetadata = JsonSerializer.Deserialize(json) ?? new ExtensionsMetadata(); + ExtensionsMetadataEnhancer.AddHintPath(extensionsMetadata.Extensions); + + foreach (ITaskItem item in AdditionalExtensions ?? Enumerable.Empty()) + { + extensionsMetadata.Extensions.AddRange(ExtensionsMetadataEnhancer.GetWebJobsExtensions(item.ItemSpec)); + } string newJson = JsonSerializer.Serialize(extensionsMetadata, _serializerOptions); File.WriteAllText(OutputPath, newJson); diff --git a/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs b/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs index e8e42cee2..e54aa995a 100644 --- a/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs +++ b/sdk/Sdk/Tasks/GenerateFunctionMetadata.cs @@ -24,7 +24,6 @@ public class GenerateFunctionMetadata : Task [Required] public string? OutputPath { get; set; } - [Required] public string? ExtensionsCsProjFilePath { get; set; } [Required] @@ -45,12 +44,16 @@ public override bool Execute() { var functionGenerator = new FunctionMetadataGenerator(MSBuildLogger); - var functions = functionGenerator.GenerateFunctionMetadata(AssemblyPath!, ReferencePaths ?? Enumerable.Empty()); + IEnumerable functions = functionGenerator.GenerateFunctionMetadata(AssemblyPath!, ReferencePaths ?? Enumerable.Empty()); + IDictionary extensions = functionGenerator.Extensions; - var extensions = functionGenerator.Extensions; - var extensionsCsProjGenerator = new ExtensionsCsprojGenerator(extensions, ExtensionsCsProjFilePath!, AzureFunctionsVersion!, TargetFrameworkIdentifier!, TargetFrameworkVersion!); + if (!string.IsNullOrEmpty(ExtensionsCsProjFilePath)) + { + // Null/empty ExtensionsCsProjFilePath means the extension project is externally provided. + var extensionsCsProjGenerator = new ExtensionsCsprojGenerator(extensions, ExtensionsCsProjFilePath!, AzureFunctionsVersion!, TargetFrameworkIdentifier!, TargetFrameworkVersion!); + extensionsCsProjGenerator.Generate(); + } - extensionsCsProjGenerator.Generate(); WriteMetadataWithRetry(functions); } catch (FunctionsMetadataGenerationException) diff --git a/sdk/Sdk/Tasks/ZipDeploy/Http/HttpResponseMessageForStatusCode.cs b/sdk/Sdk/Tasks/ZipDeploy/Http/HttpResponseMessageForStatusCode.cs index d96299877..499023ed2 100644 --- a/sdk/Sdk/Tasks/ZipDeploy/Http/HttpResponseMessageForStatusCode.cs +++ b/sdk/Sdk/Tasks/ZipDeploy/Http/HttpResponseMessageForStatusCode.cs @@ -1,7 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; @@ -28,7 +29,7 @@ public Task GetResponseBodyAsync() public IEnumerable GetHeader(string name) { - return new string[0]; + return Array.Empty(); } } } diff --git a/sdk/Sdk/Tasks/ZipDeploy/ZipDeployTask.cs b/sdk/Sdk/Tasks/ZipDeploy/ZipDeployTask.cs index 28df601f7..662052c79 100644 --- a/sdk/Sdk/Tasks/ZipDeploy/ZipDeployTask.cs +++ b/sdk/Sdk/Tasks/ZipDeploy/ZipDeployTask.cs @@ -63,7 +63,7 @@ internal System.Threading.Tasks.Task ZipDeployAsync(string zipToPublishPat return ZipDeployAsync(zipToPublishPath, userName, password, publishUrl, siteName, userAgentVersion, useBlobContainerDeploy: false, client, logMessages); } - internal async System.Threading.Tasks.Task ZipDeployAsync(string zipToPublishPath, string userName, string password, string? publishUrl, string siteName, string userAgentVersion, bool useBlobContainerDeploy, IHttpClient client, bool logMessages) + internal async System.Threading.Tasks.Task ZipDeployAsync(string zipToPublishPath, string userName, string password, string? publishUrl, string? siteName, string userAgentVersion, bool useBlobContainerDeploy, IHttpClient client, bool logMessages) { if (!File.Exists(zipToPublishPath) || client == null) { diff --git a/sdk/Sdk/Tasks/ZipDeploy/ZipDeploymentStatus.cs b/sdk/Sdk/Tasks/ZipDeploy/ZipDeploymentStatus.cs index b96138457..b95f3713a 100644 --- a/sdk/Sdk/Tasks/ZipDeploy/ZipDeploymentStatus.cs +++ b/sdk/Sdk/Tasks/ZipDeploy/ZipDeploymentStatus.cs @@ -75,7 +75,7 @@ public async Task PollDeploymentStatusAsync(string deploymentUrl, return DeployStatus.Unknown; } - await Task.Delay(TimeSpan.FromSeconds(StatusRefreshDelaySeconds)); + await Task.Delay(TimeSpan.FromSeconds(StatusRefreshDelaySeconds), tokenSource.Token); } return deployStatus; diff --git a/sdk/release_notes.md b/sdk/release_notes.md index 67afe6f17..b83fe84e1 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -4,10 +4,10 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Sdk +### Microsoft.Azure.Functions.Worker.Sdk 2.0.7 -- Changed exception handling in function invocation path to ensure fatal exceptions bubble up. +- Reversal of change to suppress generation of functions.metadata file ### Microsoft.Azure.Functions.Worker.Sdk.Generators -- +- \ No newline at end of file diff --git a/setup-e2e-tests.ps1 b/setup-e2e-tests.ps1 index 618709d55..c9cb60358 100644 --- a/setup-e2e-tests.ps1 +++ b/setup-e2e-tests.ps1 @@ -4,7 +4,7 @@ param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] - [ValidateSet("net7", "netfx")] + [ValidateSet("net8", "netfx")] $DotnetVersion, [Switch] @@ -25,66 +25,76 @@ param( $FunctionsRuntimeVersion = 4 -# A function that checks exit codes and fails script if an error is found function StopOnFailedExecution { - if ($LastExitCode) - { - exit $LastExitCode + if ($LastExitCode) + { + exit $LastExitCode } } if($SkipCoreTools) { Write-Host - Write-Host "---Skipping Core Tools download---" + Write-Host "---Skipping Core Tools download---" } else { $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() + if ($IsWindows) { $os = "win" $coreToolsURL = $env:CORE_TOOLS_URL } + elseif ($IsMacOS) { + $os = "osx" + } else { - if ($IsMacOS) { - $os = "osx" - } else { - $os = "linux" - $coreToolsURL = $env:CORE_TOOLS_URL_LINUX - } + $os = "linux" + $coreToolsURL = $env:CORE_TOOLS_URL_LINUX } - if ($UseCoreToolsBuildFromIntegrationTests.IsPresent) - { + if ($UseCoreToolsBuildFromIntegrationTests -eq $true -or $UseCoreToolsBuildFromIntegrationTests.IsPresent) { Write-Host "" - Write-Host "Install the Core Tools for Integration Tests..." - $coreToolsURL = "https://functionsintegclibuilds.blob.core.windows.net/builds/$FunctionsRuntimeVersion/latest/Azure.Functions.Cli.$os-$arch.zip" - $versionUrl = "https://functionsintegclibuilds.blob.core.windows.net/builds/$FunctionsRuntimeVersion/latest/version.txt" + Write-Host "Using Core Tools from integration test feed..." + + if ([string]::IsNullOrWhiteSpace($coreToolsURL)) { + Write-Error "CORE_TOOLS_URL (or CORE_TOOLS_URL_LINUX) is not set." + exit 1 + } } - else - { - if ([string]::IsNullOrWhiteSpace($coreToolsURL)) - { - $coreToolsURL = "https://functionsclibuilds.blob.core.windows.net/builds/$FunctionsRuntimeVersion/latest/Azure.Functions.Cli.$os-$arch.zip" - $versionUrl = "https://functionsclibuilds.blob.core.windows.net/builds/$FunctionsRuntimeVersion/latest/version.txt" + else { + Write-Host "" + Write-Host "Using latest Core Tools release from GitHub..." + + # GitHub API call for latest release + $releaseInfo = Invoke-RestMethod -Uri "https://api.github.com/repos/Azure/azure-functions-core-tools/releases/latest" -Headers @{ "User-Agent" = "PowerShell" } + + $latestVersion = $releaseInfo.tag_name + Write-Host "`nLatest Core Tools version: $latestVersion" + + # Look for zip file matching os and arch + $pattern = "Azure\.Functions\.Cli\.$os-$arch\..*\.zip$" + $asset = $releaseInfo.assets | Where-Object { + $_.name -match $pattern } + + if (-not $asset) { + Write-Error "Could not find a Core Tools .zip for OS '$os' and arch '$arch'" + exit 1 + } + + $coreToolsURL = $asset.browser_download_url } Write-Host "" Write-Host "---Downloading the Core Tools for Functions V$FunctionsRuntimeVersion---" - Write-Host "Core Tools download url: $coreToolsURL" + Write-Host "Core Tools download URL: $coreToolsURL" $FUNC_CLI_DIRECTORY = Join-Path $PSScriptRoot 'Azure.Functions.Cli' Write-Host 'Deleting Functions Core Tools if exists...' Remove-Item -Force "$FUNC_CLI_DIRECTORY.zip" -ErrorAction Ignore Remove-Item -Recurse -Force $FUNC_CLI_DIRECTORY -ErrorAction Ignore - if ($versionUrl) - { - $version = Invoke-RestMethod -Uri $versionUrl - Write-Host "Downloading Functions Core Tools (Version: $version)..." - } - $output = "$FUNC_CLI_DIRECTORY.zip" Invoke-RestMethod -Uri $coreToolsURL -OutFile $output @@ -95,11 +105,11 @@ else { & "chmod" "a+x" "$FUNC_CLI_DIRECTORY/func" } - + Write-Host "------" } -if (Test-Path $output) +if (Test-Path $output) { Remove-Item $output -Recurse -Force -ErrorAction Ignore } @@ -113,7 +123,7 @@ if ($SkipBuildOnPack -eq $true) $AdditionalPackArgs += "--no-build" } -.\tools\devpack.ps1 -E2E -AdditionalPackArgs $AdditionalPackArgs +.\tools\devpack.ps1 -DotnetVersion $DotnetVersion -E2E -AdditionalPackArgs $AdditionalPackArgs if ($SkipStorageEmulator -And $SkipCosmosDBEmulator) { @@ -121,7 +131,7 @@ if ($SkipStorageEmulator -And $SkipCosmosDBEmulator) Write-Host "---Skipping emulator startup---" Write-Host } -else +else { .\tools\start-emulators.ps1 -SkipStorageEmulator:$SkipStorageEmulator -SkipCosmosDBEmulator:$SkipCosmosDBEmulator } diff --git a/src/DotNetWorker.ApplicationInsights/DotNetWorker.ApplicationInsights.csproj b/src/DotNetWorker.ApplicationInsights/DotNetWorker.ApplicationInsights.csproj index 9602aa2eb..8f70d04b2 100644 --- a/src/DotNetWorker.ApplicationInsights/DotNetWorker.ApplicationInsights.csproj +++ b/src/DotNetWorker.ApplicationInsights/DotNetWorker.ApplicationInsights.csproj @@ -5,20 +5,19 @@ Microsoft.Azure.Functions.Worker.ApplicationInsights Microsoft.Azure.Functions.Worker.ApplicationInsights Microsoft.Azure.Functions.Worker.ApplicationInsights - 1 - 4 + 2 + 50 0 - README.md - + README.md $(BeforePack);GetReleaseNotes - - - + + + diff --git a/src/DotNetWorker.ApplicationInsights/Initializers/TelemetryConfigurationSetup.cs b/src/DotNetWorker.ApplicationInsights/Initializers/TelemetryConfigurationSetup.cs index d3b2855bf..0d4535ea7 100644 --- a/src/DotNetWorker.ApplicationInsights/Initializers/TelemetryConfigurationSetup.cs +++ b/src/DotNetWorker.ApplicationInsights/Initializers/TelemetryConfigurationSetup.cs @@ -26,18 +26,17 @@ public void Configure(TelemetryConfiguration telemetryConfiguration) private string? GetAuthenticationString() { - if (_configuration is not null && _configuration[AppInsightsAuthenticationString] is string value && value is not "") + if (_configuration is not null && _configuration[AppInsightsAuthenticationString] is { } value and not "") { return value; } - else if (Environment.GetEnvironmentVariable(AppInsightsAuthenticationString) is string envValue && envValue is not "") + + if (Environment.GetEnvironmentVariable(AppInsightsAuthenticationString) is { } envValue and not "") { return envValue; } - else - { - return null; - } + + return null; } } } diff --git a/src/DotNetWorker.ApplicationInsights/Initializers/TokenCredentialOptions.cs b/src/DotNetWorker.ApplicationInsights/Initializers/TokenCredentialOptions.cs index 0cf7f89de..2e26c9116 100644 --- a/src/DotNetWorker.ApplicationInsights/Initializers/TokenCredentialOptions.cs +++ b/src/DotNetWorker.ApplicationInsights/Initializers/TokenCredentialOptions.cs @@ -13,7 +13,7 @@ internal class TokenCredentialOptions private const string AuthClientIdKey = "ClientId"; /// - /// The client ID of an user-assigned identity. + /// The client ID of a user-assigned identity. /// /// /// This must be specified if you're using user-assigned managed identity. @@ -42,64 +42,64 @@ public static TokenCredentialOptions ParseAuthenticationString(string applicatio var tokenCredentialOptions = new TokenCredentialOptions(); bool isValidConfiguration = false; + foreach ((int, int) split in Tokenize(applicationInsightsAuthenticationString)) { (int start, int length) = split; - // Trim whitespace from start - while (length > 0 && char.IsWhiteSpace(applicationInsightsAuthenticationString[start])) - { - start++; - length--; - } + var authenticationStringToken = applicationInsightsAuthenticationString + .AsSpan(start, length) + .Trim(); // Ignore (allow) empty tokens. - if (length == 0) + if (authenticationStringToken.IsEmpty) { continue; } // Find key-value separator. - int indexOfEquals = applicationInsightsAuthenticationString.IndexOf('=', start, length); + int indexOfEquals = authenticationStringToken.IndexOf('='); if (indexOfEquals < 0) { continue; } // Extract key - int keyLength = indexOfEquals - start; - string key = applicationInsightsAuthenticationString.Substring(start, keyLength).TrimEnd(); - if (key.Length == 0) + var key = authenticationStringToken[..indexOfEquals].TrimEnd(); + if (key.IsEmpty) { // Key is blank continue; } - if (key.Equals(AuthAuthorizationKey, StringComparison.OrdinalIgnoreCase)) + // check if the span matches the string "df": + if (key.CompareTo(AuthAuthorizationKey.AsSpan(), StringComparison.OrdinalIgnoreCase) == 0) { - if (!applicationInsightsAuthenticationString.Substring(indexOfEquals + 1, length - keyLength - 1).Trim().Equals(AuthToken, StringComparison.OrdinalIgnoreCase)) + if (authenticationStringToken[(indexOfEquals + 1)..].CompareTo(AuthToken.AsSpan(), StringComparison.OrdinalIgnoreCase) != 0) { throw new InvalidCredentialException("Credential supplied is not valid for the authorization mechanism being used in ApplicationInsights."); } isValidConfiguration = true; continue; } - if (key.Equals(AuthClientIdKey, StringComparison.OrdinalIgnoreCase)) + + if (key.CompareTo(AuthClientIdKey.AsSpan(), StringComparison.OrdinalIgnoreCase) == 0) { - string clientId = applicationInsightsAuthenticationString.Substring(indexOfEquals + 1, length - keyLength - 1).Trim(); - if (!Guid.TryParse(clientId, out Guid clientIdGuid)) + var clientId = authenticationStringToken[(indexOfEquals + 1)..].Trim().ToString(); + if (!Guid.TryParse(clientId, out Guid _)) { throw new FormatException($"The Application Insights AuthenticationString {AuthClientIdKey} is not a valid GUID."); } tokenCredentialOptions.ClientId = clientId; - continue; } } + // Throw if the Authorization key is not present in the authentication string if (!isValidConfiguration) { throw new InvalidCredentialException("Authorization key is missing in the authentication string for ApplicationInsights."); } + return tokenCredentialOptions; } diff --git a/src/DotNetWorker.ApplicationInsights/release_notes.md b/src/DotNetWorker.ApplicationInsights/release_notes.md index a24d892c6..756698416 100644 --- a/src/DotNetWorker.ApplicationInsights/release_notes.md +++ b/src/DotNetWorker.ApplicationInsights/release_notes.md @@ -1,4 +1,6 @@ ## What's Changed -### Microsoft.Azure.Functions.Worker.ApplicationInsights 1.4.0 -- Updating `Azure.Identity` to 1.12.0 +### Microsoft.Azure.Functions.Worker.ApplicationInsights + +- Updating `Azure.Identity` from 1.12.0 to 1.17.0 +- Updating `Microsoft.ApplicationInsights.PerfCounterCollector` from 2.22.0 to 2.23.0 \ No newline at end of file diff --git a/src/DotNetWorker.Core/Context/BindingContext.cs b/src/DotNetWorker.Core/Context/BindingContext.cs index 106ce50bf..d5ad26c55 100644 --- a/src/DotNetWorker.Core/Context/BindingContext.cs +++ b/src/DotNetWorker.Core/Context/BindingContext.cs @@ -12,7 +12,7 @@ public abstract class BindingContext { /// /// Gets the binding data information for the current context. - /// This contains all of the trigger defined metadata. + /// This contains all the trigger defined metadata. /// public abstract IReadOnlyDictionary BindingData { get; } } diff --git a/src/DotNetWorker.Core/Context/DefaultTraceContext.cs b/src/DotNetWorker.Core/Context/DefaultTraceContext.cs index a3994901f..10e9fdcd9 100644 --- a/src/DotNetWorker.Core/Context/DefaultTraceContext.cs +++ b/src/DotNetWorker.Core/Context/DefaultTraceContext.cs @@ -1,18 +1,23 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System.Collections.Generic; + namespace Microsoft.Azure.Functions.Worker { internal sealed class DefaultTraceContext : TraceContext { - public DefaultTraceContext(string traceParent, string traceState) + public DefaultTraceContext(string traceParent, string traceState, IReadOnlyDictionary attributes) { TraceParent = traceParent; TraceState = traceState; + Attributes = attributes; } public override string TraceParent { get; } public override string TraceState { get; } + + public override IReadOnlyDictionary Attributes { get; } } } diff --git a/src/DotNetWorker.Core/Context/Features/DefaultInputConversionFeature.cs b/src/DotNetWorker.Core/Context/Features/DefaultInputConversionFeature.cs index 267d6804e..0ef6d375f 100644 --- a/src/DotNetWorker.Core/Context/Features/DefaultInputConversionFeature.cs +++ b/src/DotNetWorker.Core/Context/Features/DefaultInputConversionFeature.cs @@ -18,9 +18,12 @@ internal sealed class DefaultInputConversionFeature : IInputConversionFeature private readonly IInputConverterProvider _inputConverterProvider; private static readonly Type _inputConverterAttributeType = typeof(InputConverterAttribute); - // Users may create a POCO and specify a special converter implementation - // to be used using "InputConverter" attribute. We cache that mapping here. - // Key is assembly qualified name of POCO and Value is assembly qualified name of converter implementation. + + /// + /// Users may create a POCO and specify a special converter implementation + /// using the . We cache that mapping here. + /// Key is assembly qualified name of POCO and Value is assembly qualified name of converter implementation. + /// private static readonly ConcurrentDictionary _typeToConverterCache = new(); public DefaultInputConversionFeature(IInputConverterProvider inputConverterProvider) diff --git a/src/DotNetWorker.Core/Context/FunctionContextHttpRequestExtensions.cs b/src/DotNetWorker.Core/Context/FunctionContextHttpRequestExtensions.cs index 522580559..db8759751 100644 --- a/src/DotNetWorker.Core/Context/FunctionContextHttpRequestExtensions.cs +++ b/src/DotNetWorker.Core/Context/FunctionContextHttpRequestExtensions.cs @@ -16,7 +16,7 @@ public static class FunctionContextHttpRequestExtensions private const string HttpBindingType = "http"; /// - /// Gets the instance if the invocation is for an http trigger. + /// Gets the instance if the invocation is for an HTTP trigger. /// /// The FunctionContext instance. /// HttpRequestData instance if the invocation is http, else null diff --git a/src/DotNetWorker.Core/Context/FunctionInvocation.cs b/src/DotNetWorker.Core/Context/FunctionInvocation.cs index 0fdd1091b..306b0c2d8 100644 --- a/src/DotNetWorker.Core/Context/FunctionInvocation.cs +++ b/src/DotNetWorker.Core/Context/FunctionInvocation.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Functions.Worker { /// - /// A representation of the a single function invocation. + /// A representation of a single function invocation. /// public abstract class FunctionInvocation { diff --git a/src/DotNetWorker.Core/Context/TraceContext.cs b/src/DotNetWorker.Core/Context/TraceContext.cs index d663db340..8ffc1030a 100644 --- a/src/DotNetWorker.Core/Context/TraceContext.cs +++ b/src/DotNetWorker.Core/Context/TraceContext.cs @@ -1,6 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System.Collections.Generic; +using System.Collections.Immutable; + namespace Microsoft.Azure.Functions.Worker { /// @@ -17,5 +20,10 @@ public abstract class TraceContext /// Gets the state data. /// public abstract string TraceState { get; } + + /// + /// Gets the attributes associated with the trace. + /// + public virtual IReadOnlyDictionary Attributes => ImmutableDictionary.Empty; } } diff --git a/src/DotNetWorker.Core/Converters/Converter/DefaultConverterContext.cs b/src/DotNetWorker.Core/Converters/Converter/DefaultConverterContext.cs index 5c66a74ec..d4c2ab5ce 100644 --- a/src/DotNetWorker.Core/Converters/Converter/DefaultConverterContext.cs +++ b/src/DotNetWorker.Core/Converters/Converter/DefaultConverterContext.cs @@ -13,8 +13,8 @@ internal sealed class DefaultConverterContext : ConverterContext { public DefaultConverterContext(Type targetType, object? source, FunctionContext context, IReadOnlyDictionary properties) { - TargetType = targetType ?? throw new ArgumentNullException(nameof(context)); Source = source; + TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); FunctionContext = context ?? throw new ArgumentNullException(nameof(context)); Properties = properties ?? throw new ArgumentNullException(nameof(properties)); } diff --git a/src/DotNetWorker.Core/Converters/JsonPocoConverter.cs b/src/DotNetWorker.Core/Converters/JsonPocoConverter.cs index ebce118b6..65710d929 100644 --- a/src/DotNetWorker.Core/Converters/JsonPocoConverter.cs +++ b/src/DotNetWorker.Core/Converters/JsonPocoConverter.cs @@ -73,9 +73,9 @@ private async Task GetConversionResultFromDeserialization(byte } finally { - if (stream != null) + if (stream != null) { -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER await ((IAsyncDisposable)stream).DisposeAsync(); #else diff --git a/src/DotNetWorker.Core/Converters/MemoryConverter.cs b/src/DotNetWorker.Core/Converters/MemoryConverter.cs index 55685f422..6915bb322 100644 --- a/src/DotNetWorker.Core/Converters/MemoryConverter.cs +++ b/src/DotNetWorker.Core/Converters/MemoryConverter.cs @@ -18,7 +18,7 @@ public ValueTask ConvertAsync(ConverterContext context) if (context.TargetType.IsAssignableFrom(typeof(string))) { -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER var target = Encoding.UTF8.GetString(sourceMemory.Span); #else var target = Encoding.UTF8.GetString(sourceMemory.Span.ToArray()); diff --git a/src/DotNetWorker.Core/Diagnostics/ActivityExtensions.cs b/src/DotNetWorker.Core/Diagnostics/ActivityExtensions.cs deleted file mode 100644 index 2e53a05c6..000000000 --- a/src/DotNetWorker.Core/Diagnostics/ActivityExtensions.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Linq.Expressions; -using System.Reflection; - -namespace Microsoft.Azure.Functions.Worker.Diagnostics -{ - internal static class ActivityExtensions - { - - private static readonly Action _setSpanId; - private static readonly Action _setId; - private static readonly Action _setTraceId; - private static readonly Action _setRootId; - - static ActivityExtensions() - { - BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance; - var activityType = typeof(Activity); - - // Empty setter serves as a safe fallback mechanism to handle cases where the field is not available. - _setSpanId = activityType.GetField("_spanId", flags)?.CreateSetter() ?? ((_, _) => { /* Ignore */ }); - _setId = activityType.GetField("_id", flags)?.CreateSetter() ?? ((_, _) => { /* Ignore */ }); - _setRootId = activityType.GetField("_rootId", flags)?.CreateSetter() ?? ((_, _) => { /* Ignore */ }); - _setTraceId = activityType.GetField("_traceId", flags)?.CreateSetter() ?? ((_, _) => { /* Ignore */ }); - } - - /// - /// Records an exception as an ActivityEvent. - /// - /// The Activity. - /// The exception. - /// If the exception is re-thrown out of the current span, set to true. - /// See https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/exceptions/#recording-an-exception. - /// - public static void RecordException(this Activity activity, Exception ex, bool escaped) - { - if (ex == null) - { - return; - } - - var tagsCollection = new ActivityTagsCollection - { - { TraceConstants.AttributeExceptionType, ex.GetType().FullName }, - { TraceConstants.AttributeExceptionStacktrace, ex.ToString() } - }; - - if (!string.IsNullOrWhiteSpace(ex.Message)) - { - tagsCollection.Add(TraceConstants.AttributeExceptionMessage, ex.Message); - } - - if (escaped) - { - tagsCollection.Add(TraceConstants.AttributeExceptionEscaped, true); - } - - activity?.AddEvent(new ActivityEvent(TraceConstants.AttributeExceptionEventName, default, tagsCollection)); - } - - public static void SetId(this Activity activity, string id) - => _setId(activity, id); - - public static void SetSpanId(this Activity activity, string spanId) - => _setSpanId(activity, spanId); - - public static void SetRootId(this Activity activity, string rootId) - => _setRootId(activity, rootId); - - public static void SetTraceId(this Activity activity, string traceId) - => _setTraceId(activity, traceId); - } - - internal static class FieldInfoExtensionMethods - { - /// - /// Create a re-usable setter for a . - /// When cached and reused, This is quicker than using . - /// - /// The target type of the object. - /// The value type of the field. - /// The field info. - /// A re-usable action to set the field. - internal static Action CreateSetter(this FieldInfo fieldInfo) - { - if (fieldInfo == null) - { - throw new ArgumentNullException(nameof(fieldInfo)); - } - - ParameterExpression targetExp = Expression.Parameter(typeof(TTarget), "target"); - Expression source = targetExp; - - if (fieldInfo.DeclaringType is { } t && t != typeof(TTarget)) - { - source = Expression.Convert(targetExp, t); - } - - // Creating the setter to set the value to the field - ParameterExpression valueExp = Expression.Parameter(typeof(TValue), "value"); - MemberExpression fieldExp = Expression.Field(source, fieldInfo); - BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp); - return Expression.Lambda>(assignExp, targetExp, valueExp).Compile(); - } - } -} diff --git a/src/DotNetWorker.Core/Diagnostics/FunctionActivitySourceFactory.cs b/src/DotNetWorker.Core/Diagnostics/FunctionActivitySourceFactory.cs deleted file mode 100644 index 6b472deb7..000000000 --- a/src/DotNetWorker.Core/Diagnostics/FunctionActivitySourceFactory.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using Microsoft.Extensions.Options; - -namespace Microsoft.Azure.Functions.Worker.Diagnostics -{ - internal class FunctionActivitySourceFactory - { - private static readonly ActivitySource _activitySource = new(TraceConstants.FunctionsActivitySource, TraceConstants.FunctionsActivitySourceVersion); - private readonly string _schemaVersionUrl; - private readonly Lazy> _attributeMap; - - public FunctionActivitySourceFactory(IOptions options) - { - _attributeMap = new Lazy>(() => GetMapping(options.Value.OpenTelemetrySchemaVersion)); - _schemaVersionUrl = TraceConstants.OpenTelemetrySchemaMap[options.Value.OpenTelemetrySchemaVersion]; - } - - public Activity? StartInvoke(FunctionContext context) - { - Activity? activity = null; - - if (_activitySource.HasListeners()) - { - ActivityContext.TryParse(context.TraceContext.TraceParent, context.TraceContext.TraceState, out ActivityContext activityContext); - - activity = _activitySource.StartActivity(TraceConstants.FunctionsInvokeActivityName, ActivityKind.Server, activityContext, - tags: GetTags(context)); - } - - return activity; - } - - /// - /// Provides key mappings for different schema versions. For example, in early versions the invocation id may be - /// represented by "faas.execution" and then later change to "faas.invocation". We want to allow for each of these as - /// exporters may be relying on them. - /// - /// - /// The mapped key name. - /// - private static IReadOnlyDictionary GetMapping(OpenTelemetrySchemaVersion schemaVersion) - { - return schemaVersion switch - { - OpenTelemetrySchemaVersion.v1_17_0 => ImmutableDictionary.Empty, - _ => throw new InvalidOperationException("Schema not supported."), - }; - } - - private IEnumerable> GetTags(FunctionContext context) - { - yield return new(TraceConstants.AttributeSchemaUrl, _schemaVersionUrl); - - string GetKeyMapping(string key) => _attributeMap.Value.GetValueOrDefault(key, key); - - // Using as an example of how to map if schemas change. - yield return new(GetKeyMapping(TraceConstants.AttributeFaasExecution), context.InvocationId); - } - } -} diff --git a/src/DotNetWorker.Core/Diagnostics/FunctionInvocationScope.cs b/src/DotNetWorker.Core/Diagnostics/FunctionInvocationScope.cs deleted file mode 100644 index 49e5d7e17..000000000 --- a/src/DotNetWorker.Core/Diagnostics/FunctionInvocationScope.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Microsoft.Azure.Functions.Worker.Diagnostics -{ - internal class FunctionInvocationScope : IReadOnlyList> - { - internal const string FunctionInvocationIdKey = "AzureFunctions_InvocationId"; - internal const string FunctionNameKey = "AzureFunctions_FunctionName"; - - private readonly string _invocationId; - private readonly string _functionName; - - private string? _cachedToString; - - public FunctionInvocationScope(string functionName, string invocationid) - { - _functionName = functionName; - _invocationId = invocationid; - } - - public KeyValuePair this[int index] - { - get - { - return index switch - { - 0 => new KeyValuePair(FunctionInvocationIdKey, _invocationId), - 1 => new KeyValuePair(FunctionNameKey, _functionName), - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - } - } - - public int Count => 2; - - public IEnumerator> GetEnumerator() - { - for (var i = 0; i < Count; ++i) - { - yield return this[i]; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public override string ToString() - { - if (_cachedToString == null) - { - _cachedToString = FormattableString.Invariant($"{FunctionNameKey}:{_functionName} {FunctionInvocationIdKey}:{_invocationId}"); - } - - return _cachedToString; - } - } -} diff --git a/src/DotNetWorker.Core/Diagnostics/OpenTelemetrySchemaVersion.cs b/src/DotNetWorker.Core/Diagnostics/OpenTelemetrySchemaVersion.cs index 8b2e17a92..c24727bfd 100644 --- a/src/DotNetWorker.Core/Diagnostics/OpenTelemetrySchemaVersion.cs +++ b/src/DotNetWorker.Core/Diagnostics/OpenTelemetrySchemaVersion.cs @@ -1,10 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. namespace Microsoft.Azure.Functions.Worker.Diagnostics { internal enum OpenTelemetrySchemaVersion { - v1_17_0 + V1_17_0, + V1_37_0 } } diff --git a/src/DotNetWorker.Core/Diagnostics/Telemetry/IFunctionTelemetryProvider.cs b/src/DotNetWorker.Core/Diagnostics/Telemetry/IFunctionTelemetryProvider.cs new file mode 100644 index 000000000..e9560ffb0 --- /dev/null +++ b/src/DotNetWorker.Core/Diagnostics/Telemetry/IFunctionTelemetryProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Azure.Functions.Worker.Diagnostics; + +/// +/// Provides methods for telemetry data collection related to function invocations. +/// +/// This interface defines methods to retrieve telemetry attributes and manage the activity lifecycle for +/// function invocations, enabling detailed monitoring and diagnostics. +internal interface IFunctionTelemetryProvider +{ + /// + /// Returns the attributes to be applied to the Scope for this invocation. + /// + IEnumerable> GetScopeAttributes(FunctionContext ctx); + + /// + /// Returns the attributes to be applied to the Activity for this invocation. + /// + IEnumerable> GetTagAttributes(FunctionContext ctx); + + /// + /// Starts the Activity for this invocation. + /// + Activity? StartActivityForInvocation(FunctionContext ctx); +} diff --git a/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProvider.cs b/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProvider.cs new file mode 100644 index 000000000..d5c224dad --- /dev/null +++ b/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProvider.cs @@ -0,0 +1,118 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Azure.Functions.Worker.Diagnostics; + +internal abstract class TelemetryProvider : IFunctionTelemetryProvider +{ + private static readonly ActivitySource _source + = new(TraceConstants.ActivityAttributes.Name, TraceConstants.ActivityAttributes.Version); + + protected abstract OpenTelemetrySchemaVersion SchemaVersion { get; } + + protected abstract ActivityKind Kind { get; } + + /// + /// Creates a telemetry provider based on the provided schema version string. + /// Returns the default (1.17.0) if no version is provided. + /// + /// + /// + public static TelemetryProvider Create(string? schema = null) + { + if (string.IsNullOrWhiteSpace(schema)) + { + return Create(OpenTelemetrySchemaVersion.V1_17_0); + } + + var version = ParseSchemaVersion(schema!); + return Create(version); + } + + /// + /// Returns a telemetry provider for the specified version. + /// + /// + /// + /// + public static TelemetryProvider Create(OpenTelemetrySchemaVersion version) + { + return version switch + { + OpenTelemetrySchemaVersion.V1_17_0 => new TelemetryProviderV1_17_0(), + OpenTelemetrySchemaVersion.V1_37_0 => new TelemetryProviderV1_37_0(), + _ => throw new ArgumentException($"Unsupported OpenTelemetry schema version: {version}") + }; + } + + /// + /// Starts an activity for the function invocation. + /// + /// + /// + public Activity? StartActivityForInvocation(FunctionContext context) + { + if (!_source.HasListeners()) + { + return null; + } + + ActivityContext.TryParse( + context.TraceContext.TraceParent, + context.TraceContext.TraceState, + out var parent); + + // If there is no parent, we still want to create a new root activity. + return _source.StartActivity( + GetActivityName(context), + Kind, + parent, + tags: GetTagAttributes(context)!); + } + + /// + /// Returns common scope attributes for a schema versions. + /// + /// + /// + public virtual IEnumerable> GetScopeAttributes(FunctionContext context) + { + // Live-logs session + if (context.TraceContext.Attributes.TryGetValue(TraceConstants.InternalKeys.AzFuncLiveLogsSessionId, out var liveId) + && !string.IsNullOrWhiteSpace(liveId)) + { + yield return new(TraceConstants.InternalKeys.AzFuncLiveLogsSessionId, liveId); + } + } + + protected virtual string GetActivityName(FunctionContext context) + { + return TraceConstants.ActivityAttributes.InvokeActivityName; + } + + /// + /// Returns common tag attributes for a schema versions. + /// + /// + /// + public abstract IEnumerable> GetTagAttributes(FunctionContext context); + + /// + /// Maps only known version strings to the enum. + /// If the string is anything else (and was explicitly set), we throw. + /// + private static OpenTelemetrySchemaVersion ParseSchemaVersion(string version) + { + return version switch + { + "1.17.0" => OpenTelemetrySchemaVersion.V1_17_0, + "1.37.0" => OpenTelemetrySchemaVersion.V1_37_0, + _ => throw new ArgumentException( + $"Invalid OpenTelemetry schema version '{version}'. ", nameof(version)) + }; + } +} diff --git a/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProviderV1_17_0.cs b/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProviderV1_17_0.cs new file mode 100644 index 000000000..1e5517253 --- /dev/null +++ b/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProviderV1_17_0.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Azure.Functions.Worker.Diagnostics; + +internal sealed class TelemetryProviderV1_17_0 : TelemetryProvider +{ + private static readonly KeyValuePair SchemaUrlAttribute = + new(TraceConstants.OTelAttributes_1_17_0.SchemaUrl, TraceConstants.OTelAttributes_1_17_0.SchemaVersion); + + protected override OpenTelemetrySchemaVersion SchemaVersion + => OpenTelemetrySchemaVersion.V1_17_0; + + protected override ActivityKind Kind + => ActivityKind.Server; + + public override IEnumerable> GetScopeAttributes(FunctionContext context) + { + foreach (var kv in base.GetScopeAttributes(context)) + { + yield return kv; + } + + yield return SchemaUrlAttribute; + yield return new(TraceConstants.InternalKeys.FunctionInvocationId, context.InvocationId); + yield return new(TraceConstants.InternalKeys.FunctionName, context.FunctionDefinition.Name); + } + + public override IEnumerable> GetTagAttributes(FunctionContext context) + { + yield return SchemaUrlAttribute; + yield return new(TraceConstants.OTelAttributes_1_17_0.InvocationId, context.InvocationId); + } +} diff --git a/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProviderV1_37_0.cs b/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProviderV1_37_0.cs new file mode 100644 index 000000000..64cca693f --- /dev/null +++ b/src/DotNetWorker.Core/Diagnostics/Telemetry/TelemetryProviderV1_37_0.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Azure.Functions.Worker.Diagnostics; + +internal sealed class TelemetryProviderV1_37_0 : TelemetryProvider +{ + private static readonly KeyValuePair SchemaUrlAttribute = + new(TraceConstants.OTelAttributes_1_37_0.SchemaUrl, TraceConstants.OTelAttributes_1_37_0.SchemaVersion); + protected override OpenTelemetrySchemaVersion SchemaVersion + => OpenTelemetrySchemaVersion.V1_37_0; + + protected override ActivityKind Kind + => ActivityKind.Internal; + + public override IEnumerable> GetScopeAttributes(FunctionContext context) + { + foreach (var kv in base.GetScopeAttributes(context)) + { + yield return kv; + } + + foreach (var kv in GetCommonAttributes(context)) + { + yield return kv; + } + } + + public override IEnumerable> GetTagAttributes(FunctionContext context) + { + foreach (var kv in GetCommonAttributes(context)) + { + yield return kv; + } + } + + protected override string GetActivityName(FunctionContext context) + { + return $"{TraceConstants.ActivityAttributes.FunctionActivityName} {context.FunctionDefinition.Name}"; + } + + private IEnumerable> GetCommonAttributes(FunctionContext context) + { + yield return SchemaUrlAttribute; + yield return new(TraceConstants.OTelAttributes_1_37_0.InvocationId, context.InvocationId); + yield return new(TraceConstants.OTelAttributes_1_37_0.FunctionName, context.FunctionDefinition.Name); + + if (context.TraceContext.Attributes.TryGetValue(TraceConstants.InternalKeys.HostInstanceId, out var host) + && !string.IsNullOrEmpty(host)) + { + yield return new(TraceConstants.OTelAttributes_1_37_0.Instance, host); + } + } +} diff --git a/src/DotNetWorker.Core/Diagnostics/TraceConstants.cs b/src/DotNetWorker.Core/Diagnostics/TraceConstants.cs index c8d7ecafc..f2c086f6e 100644 --- a/src/DotNetWorker.Core/Diagnostics/TraceConstants.cs +++ b/src/DotNetWorker.Core/Diagnostics/TraceConstants.cs @@ -1,31 +1,58 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; -namespace Microsoft.Azure.Functions.Worker.Diagnostics +namespace Microsoft.Azure.Functions.Worker.Diagnostics; + +internal static class TraceConstants { - internal class TraceConstants + public static class ActivityAttributes + { + public static readonly string Version = typeof(ActivityAttributes).Assembly.GetName().Version?.ToString() ?? string.Empty; + public const string Name = "Microsoft.Azure.Functions.Worker"; + public const string InvokeActivityName = "Invoke"; + public const string FunctionActivityName = "function"; + } + + public static class ExceptionAttributes + { + public const string EventName = "exception"; + public const string Type = "exception.type"; + public const string Message = "exception.message"; + public const string Stacktrace = "exception.stacktrace"; + public const string Escaped = "exception.escaped"; + } + + public static class OTelAttributes_1_17_0 + { + // v1.17.0 + public const string InvocationId = "faas.execution"; + public const string SchemaUrl = "az.schema_url"; + public const string SchemaVersion = "https://opentelemetry.io/schemas/1.17.0"; + } + + public static class OTelAttributes_1_37_0 + { + // v1.37.0 + public const string InvocationId = "faas.invocation_id"; + public const string FunctionName = "faas.name"; + public const string Instance = "faas.instance"; + public const string SchemaUrl = "schema.url"; + public const string SchemaVersion = "https://opentelemetry.io/schemas/1.37.0"; + } + + public static class InternalKeys + { + public const string FunctionInvocationId = "AzureFunctions_InvocationId"; + public const string FunctionName = "AzureFunctions_FunctionName"; + public const string HostInstanceId = "HostInstanceId"; + public const string AzFuncLiveLogsSessionId = "#AzFuncLiveLogsSessionId"; + } + + public static class CapabilityFlags { - public const string FunctionsActivitySource = "Microsoft.Azure.Functions.Worker"; - public const string FunctionsActivitySourceVersion = "1.0.0.0"; - public const string FunctionsInvokeActivityName = "Invoke"; - - public const string AttributeExceptionEventName = "exception"; - public const string AttributeExceptionType = "exception.type"; - public const string AttributeExceptionMessage = "exception.message"; - public const string AttributeExceptionStacktrace = "exception.stacktrace"; - public const string AttributeExceptionEscaped = "exception.escaped"; - - public const string AttributeSchemaUrl = "az.schema_url"; - public static IReadOnlyDictionary OpenTelemetrySchemaMap = - new Dictionary() - { - [OpenTelemetrySchemaVersion.v1_17_0] = "https://opentelemetry.io/schemas/1.17.0" - }; - - // from: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/faas/ - // https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/faas/ - public const string AttributeFaasExecution = "faas.execution"; + public const string WorkerOTelEnabled = "WorkerOpenTelemetryEnabled"; + public const string WorkerOTelSchemaVersion = "WorkerOpenTelemetrySchemaVersion"; } } diff --git a/src/DotNetWorker.Core/DotNetWorker.Core.csproj b/src/DotNetWorker.Core/DotNetWorker.Core.csproj index 3bcc803ad..a0fb1b759 100644 --- a/src/DotNetWorker.Core/DotNetWorker.Core.csproj +++ b/src/DotNetWorker.Core/DotNetWorker.Core.csproj @@ -2,15 +2,15 @@ Library - net5.0;netstandard2.0 + net6.0;net8.0;net9.0;net10.0;netstandard2.0 Microsoft.Azure.Functions.Worker.Core This library provides the core functionality to build an Azure Functions .NET Worker, adding support for the isolated, out-of-process execution model. Microsoft.Azure.Functions.Worker.Core Microsoft.Azure.Functions.Worker.Core true - 19 + 2 + 51 0 - @@ -21,17 +21,13 @@ - - - - - + + - - - - + + + diff --git a/src/DotNetWorker.Core/FunctionMetadata/DefaultFunctionMetadataManager.cs b/src/DotNetWorker.Core/FunctionMetadata/DefaultFunctionMetadataManager.cs new file mode 100644 index 000000000..ff4e03cb4 --- /dev/null +++ b/src/DotNetWorker.Core/FunctionMetadata/DefaultFunctionMetadataManager.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.Functions.Worker.Core.FunctionMetadata +{ + internal sealed class DefaultFunctionMetadataManager : IFunctionMetadataManager + { + private readonly IFunctionMetadataProvider _functionMetadataProvider; + private readonly ImmutableArray _transformers; + private readonly ILogger _logger; + + public DefaultFunctionMetadataManager(IFunctionMetadataProvider functionMetadataProvider, + IEnumerable transformers, + ILogger logger) + { + _functionMetadataProvider = functionMetadataProvider; + _transformers = transformers.ToImmutableArray(); + _logger = logger; + } + + public async Task> GetFunctionMetadataAsync(string directory) + { + ImmutableArray functionMetadata = await _functionMetadataProvider.GetFunctionMetadataAsync(directory); + + return ApplyTransforms(functionMetadata); + } + + private ImmutableArray ApplyTransforms(ImmutableArray functionMetadata) + { + // Return early if there are no transformers to apply + if (_transformers.Length == 0) + { + return functionMetadata; + } + + var metadataResult = functionMetadata.ToBuilder(); + + foreach (var transformer in _transformers) + { + try + { + _logger?.LogTrace("Applying metadata transformer: {Transformer}.", transformer.Name); + transformer.Transform(metadataResult); + } + catch (Exception exc) + { + _logger?.LogError(exc, "Metadata transformer '{Transformer}' failed.", transformer.Name); + throw; + } + } + + return metadataResult.ToImmutable(); + } + } +} diff --git a/src/DotNetWorker.Core/FunctionMetadata/IFunctionMetadataManager.cs b/src/DotNetWorker.Core/FunctionMetadata/IFunctionMetadataManager.cs new file mode 100644 index 000000000..c81c26070 --- /dev/null +++ b/src/DotNetWorker.Core/FunctionMetadata/IFunctionMetadataManager.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Functions.Worker.Core.FunctionMetadata +{ + /// + /// Manages function metadata, providing functionality that combines metadata from the registered provider and metadata transforms. + /// + public interface IFunctionMetadataManager + { + /// + /// Retrieves all function metadata for the current application. + /// + /// A representing the asynchronous metadata retrieval operation, where the result is an . + Task> GetFunctionMetadataAsync(string directory); + } +} diff --git a/src/DotNetWorker.Core/FunctionMetadata/IFunctionMetadataTransformer.cs b/src/DotNetWorker.Core/FunctionMetadata/IFunctionMetadataTransformer.cs new file mode 100644 index 000000000..98cdfdd42 --- /dev/null +++ b/src/DotNetWorker.Core/FunctionMetadata/IFunctionMetadataTransformer.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; + +/// +/// Defines a contract for transforming instances for Azure Functions. +/// Implementations can modify, augment, or filter function metadata before it is used by the host. +/// +public interface IFunctionMetadataTransformer +{ + /// + /// Gets the name of the transformer. + /// + public string Name { get; } + + /// + /// Transforms the provided collection of instances. + /// + /// The original collection of function metadata. This collection may be modified. + void Transform(IList original); +} diff --git a/src/DotNetWorker.Core/FunctionsApplication.cs b/src/DotNetWorker.Core/FunctionsApplication.cs index 11db9d003..40157f9c0 100644 --- a/src/DotNetWorker.Core/FunctionsApplication.cs +++ b/src/DotNetWorker.Core/FunctionsApplication.cs @@ -1,9 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Diagnostics; @@ -22,7 +23,7 @@ internal partial class FunctionsApplication : IFunctionsApplication private readonly IOptions _workerOptions; private readonly ILogger _logger; private readonly IWorkerDiagnostics _diagnostics; - private readonly FunctionActivitySourceFactory _functionActivitySourceFactory; + private readonly IFunctionTelemetryProvider _functionTelemetryProvider; public FunctionsApplication( FunctionExecutionDelegate functionExecutionDelegate, @@ -30,14 +31,14 @@ public FunctionsApplication( IOptions workerOptions, ILogger logger, IWorkerDiagnostics diagnostics, - FunctionActivitySourceFactory functionActivitySourceFactory) + IFunctionTelemetryProvider functionTelemetryProvider) { _functionExecutionDelegate = functionExecutionDelegate ?? throw new ArgumentNullException(nameof(functionExecutionDelegate)); _functionContextFactory = functionContextFactory ?? throw new ArgumentNullException(nameof(functionContextFactory)); _workerOptions = workerOptions ?? throw new ArgumentNullException(nameof(workerOptions)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); - _functionActivitySourceFactory = functionActivitySourceFactory ?? throw new ArgumentNullException(nameof(functionActivitySourceFactory)); + _functionTelemetryProvider = functionTelemetryProvider ?? throw new ArgumentNullException(nameof(functionTelemetryProvider)); } public FunctionContext CreateContext(IInvocationFeatures features, CancellationToken token = default) @@ -67,29 +68,8 @@ public void LoadFunction(FunctionDefinition definition) public async Task InvokeFunctionAsync(FunctionContext context) { - Activity? activity = null; - - if (Activity.Current is null) - { - // This will act as an internal activity that represents remote Host activity. This cannot be tracked as this is not associate to an ActivitySource. - activity = new Activity(nameof(InvokeFunctionAsync)); - activity.Start(); - - if (ActivityContext.TryParse(context.TraceContext.TraceParent, context.TraceContext.TraceState, true, out ActivityContext activityContext)) - { - activity.SetId(context.TraceContext.TraceParent); - activity.SetSpanId(activityContext.SpanId.ToString()); - activity.SetTraceId(activityContext.TraceId.ToString()); - activity.SetRootId(activityContext.TraceId.ToString()); - activity.ActivityTraceFlags = activityContext.TraceFlags; - activity.TraceStateString = activityContext.TraceState; - } - } - - var scope = new FunctionInvocationScope(context.FunctionDefinition.Name, context.InvocationId); - - using var logScope = _logger.BeginScope(scope); - using Activity? invokeActivity = _functionActivitySourceFactory.StartInvoke(context); + using var logScope = _logger.BeginScope(_functionTelemetryProvider.GetScopeAttributes(context).ToList()); + using Activity? invokeActivity = _functionTelemetryProvider.StartActivityForInvocation(context); try { @@ -103,9 +83,6 @@ public async Task InvokeFunctionAsync(FunctionContext context) throw; } - - invokeActivity?.Stop(); - activity?.Stop(); } } } diff --git a/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs b/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs index 8ee1643de..66fe25808 100644 --- a/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs +++ b/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; @@ -10,13 +10,13 @@ using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Converters; using Microsoft.Azure.Functions.Worker.Core; +using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; using Microsoft.Azure.Functions.Worker.Diagnostics; using Microsoft.Azure.Functions.Worker.Invocation; using Microsoft.Azure.Functions.Worker.Logging; using Microsoft.Azure.Functions.Worker.OutputBindings; using Microsoft.Azure.Functions.Worker.Pipeline; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -71,6 +71,9 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerCore(this ISe // Worker initialization service services.AddHostedService(); + // Worker metadata management + services.TryAddSingleton(); + // Default serializer settings services.AddOptions(); services.TryAddEnumerable(ServiceDescriptor.Transient, WorkerOptionsSetup>()); @@ -81,8 +84,7 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerCore(this ISe services.TryAddSingleton(s => s.GetRequiredService()); services.TryAddSingleton(s => s.GetRequiredService()); services.TryAddSingleton(s => s.GetRequiredService()); - services.TryAddSingleton(); - + if (configure != null) { services.Configure(configure); @@ -111,6 +113,8 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerCore(this ISe RunExtensionStartupCode(builder); } + services.AddFunctionTelemetry(); + return builder; } @@ -123,6 +127,29 @@ internal static IServiceCollection AddDefaultInputConvertersToWorkerOptions(this return services; } + /// + /// Adds function telemetry services to the specified . + /// + /// This method registers a singleton + /// implementation based on the OpenTelemetry schema version specified in the worker options. The schema version is determined + /// from the "WorkerOpenTelemetrySchemaVersion" capability. If the schema version is unsupported, an is thrown. + /// The to which the telemetry services are added. + /// The modified with telemetry services registered. + /// Thrown if the specified OpenTelemetry schema version is unsupported. + internal static IServiceCollection AddFunctionTelemetry(this IServiceCollection services) + { + services.TryAddSingleton(sp => + { + WorkerOptions options = sp.GetRequiredService>().Value; + + options.Capabilities.TryGetValue(TraceConstants.CapabilityFlags.WorkerOTelSchemaVersion, out var schemaVersion); + return TelemetryProvider.Create(schemaVersion); + }); + + return services; + } + /// /// Run extension startup execution code. /// Our source generator creates a class(WorkerExtensionStartupCodeExecutor) diff --git a/src/DotNetWorker.Core/Hosting/WorkerCapabilities.cs b/src/DotNetWorker.Core/Hosting/WorkerCapabilities.cs new file mode 100644 index 000000000..d5b291191 --- /dev/null +++ b/src/DotNetWorker.Core/Hosting/WorkerCapabilities.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.Functions.Worker +{ + internal static class WorkerCapabilities + { + internal const string EnableUserCodeException = "EnableUserCodeException"; + internal const string HandlesInvocationCancelMessage = "HandlesInvocationCancelMessage"; + internal const string HandlesWorkerTerminateMessage = "HandlesWorkerTerminateMessage"; + internal const string HandlesWorkerWarmupMessage = "HandlesWorkerWarmupMessage"; + internal const string IncludeEmptyEntriesInMessagePayload = "IncludeEmptyEntriesInMessagePayload"; + internal const string RawHttpBodyBytes = "RawHttpBodyBytes"; + internal const string RpcHttpBodyOnly = "RpcHttpBodyOnly"; + internal const string RpcHttpTriggerMetadataRemoved = "RpcHttpTriggerMetadataRemoved"; + internal const string TypedDataCollection = "TypedDataCollection"; + internal const string UseNullableValueDictionaryForHttp = "UseNullableValueDictionaryForHttp"; + internal const string WorkerStatus = "WorkerStatus"; + } +} diff --git a/src/DotNetWorker.Core/Hosting/WorkerOptions.cs b/src/DotNetWorker.Core/Hosting/WorkerOptions.cs index acd500c8b..a360e4183 100644 --- a/src/DotNetWorker.Core/Hosting/WorkerOptions.cs +++ b/src/DotNetWorker.Core/Hosting/WorkerOptions.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Text.Json; using Azure.Core.Serialization; @@ -10,7 +11,7 @@ namespace Microsoft.Azure.Functions.Worker { /// /// An options class for configuring the worker. - /// + /// public class WorkerOptions { /// @@ -30,14 +31,18 @@ public class WorkerOptions public IDictionary Capabilities { get; } = new Dictionary() { // Enable these by default, although they are not strictly required and can be removed - { "HandlesWorkerTerminateMessage", bool.TrueString }, - { "HandlesInvocationCancelMessage", bool.TrueString } + { WorkerCapabilities.HandlesWorkerTerminateMessage, bool.TrueString }, + { WorkerCapabilities.HandlesInvocationCancelMessage, bool.TrueString }, + { WorkerCapabilities.IncludeEmptyEntriesInMessagePayload, bool.TrueString }, + { WorkerCapabilities.EnableUserCodeException, bool.TrueString } }; /// - /// Gets or sets the flag for opting in to unwrapping user-code-thrown - /// exceptions when they are surfaced to the Host. + /// Gets or sets a value indicating whether exceptions thrown by user code should be unwrapped + /// and surfaced to the Host as their original exception type, instead of being wrapped in an RpcException. + /// The default value is . /// + [Obsolete("This is now the default behavior. This property may be unavailable in future releases.", false)] public bool EnableUserCodeException { get => GetBoolCapability(nameof(EnableUserCodeException)); @@ -49,7 +54,7 @@ public bool EnableUserCodeException /// For example, if a set of entries were sent to a messaging service such as Service Bus or Event Hub and your function /// app has a Service bus trigger or Event hub trigger, only the non-empty entries from the payload will be sent to the /// function code as trigger data when this setting value is . When it is , - /// All entries will be sent to the function code as it is. Default value for this setting is . + /// all entries will be sent to the function code as it is. Default value for this setting is . /// public bool IncludeEmptyEntriesInMessagePayload { @@ -61,7 +66,7 @@ public bool IncludeEmptyEntriesInMessagePayload /// Gets or sets a value that determines the schema to use when generating Activities. Currently internal as there is only /// one schema, but stubbing this out for future use. /// - internal OpenTelemetrySchemaVersion OpenTelemetrySchemaVersion { get; set; } = OpenTelemetrySchemaVersion.v1_17_0; + internal OpenTelemetrySchemaVersion OpenTelemetrySchemaVersion { get; set; } = OpenTelemetrySchemaVersion.V1_17_0; private bool GetBoolCapability(string name) { diff --git a/src/DotNetWorker.Core/Http/HttpRequestDataExtensions.cs b/src/DotNetWorker.Core/Http/HttpRequestDataExtensions.cs index f8b50c469..760c6e31d 100644 --- a/src/DotNetWorker.Core/Http/HttpRequestDataExtensions.cs +++ b/src/DotNetWorker.Core/Http/HttpRequestDataExtensions.cs @@ -1,140 +1,140 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core.Serialization; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Microsoft.Azure.Functions.Worker.Http -{ - /// - /// Provides extension methods to work with an instance. - /// - public static class HttpRequestDataExtensions - { - /// - /// Reads the body payload as a string. - /// - /// The request from which to read. - /// The encoding to use when reading the string. Defaults to UTF-8 - /// A that represents the asynchronous read operation. - public static async Task ReadAsStringAsync(this HttpRequestData request, Encoding? encoding = null) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - if (request.Body is null) - { - return null; - } - - using (var reader = new StreamReader(request.Body, bufferSize: 1024, detectEncodingFromByteOrderMarks: true, encoding: encoding ?? Encoding.UTF8, leaveOpen: true)) - { - return await reader.ReadToEndAsync(); - } - } - - /// - /// Reads the body payload as a string. - /// - /// The request from which to read. - /// The encoding to use when reading the string. Defaults to UTF-8 - /// A that represents request body. - public static string? ReadAsString(this HttpRequestData request, Encoding? encoding = null) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - if (request.Body is null) - { - return null; - } - - using (var reader = new StreamReader(request.Body, bufferSize: 1024, detectEncodingFromByteOrderMarks: true, encoding: encoding ?? Encoding.UTF8, leaveOpen: true)) - { - return reader.ReadToEnd(); - } - } - - /// - /// Reads the request using the default configured for this worker. - /// - /// The target type of the JSON value. - /// The request to be read. - /// A token that may be used to cancel the read operation. - /// A representing the asynchronous operation. - public static ValueTask ReadFromJsonAsync(this HttpRequestData request, CancellationToken cancellationToken = default) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - ObjectSerializer serializer = request.FunctionContext.InstanceServices.GetService>()?.Value?.Serializer - ?? throw new InvalidOperationException("A serializer is not configured for the worker."); - - return ReadFromJsonAsync(request, serializer, cancellationToken); - } - - /// - /// Reads the request using the provided . - /// - /// The target type of the JSON value. - /// The request to be read. - /// The to use for the deserialization. - /// A token that may be used to cancel the read operation. - /// A representing the asynchronous operation. - public static ValueTask ReadFromJsonAsync(this HttpRequestData request, ObjectSerializer serializer, CancellationToken cancellationToken = default) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - if (serializer is null) - { - throw new ArgumentNullException(nameof(serializer)); - } - - ValueTask result = serializer.DeserializeAsync(request.Body, typeof(T), cancellationToken); - - static T? TryCast(object? value) - { - return value != null - ? (T)value - : default; - } - - if (result.IsCompletedSuccessfully) - { - return new ValueTask(TryCast(result.Result)); - } - - return new ValueTask(result.AsTask().ContinueWith(t => TryCast(t.Result))); +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker.Http +{ + /// + /// Provides extension methods to work with an instance. + /// + public static class HttpRequestDataExtensions + { + /// + /// Reads the body payload as a string. + /// + /// The request from which to read. + /// The encoding to use when reading the string. Defaults to UTF-8 + /// A that represents the asynchronous read operation. + public static async Task ReadAsStringAsync(this HttpRequestData request, Encoding? encoding = null) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (request.Body is null) + { + return null; + } + + using (var reader = new StreamReader(request.Body, bufferSize: 1024, detectEncodingFromByteOrderMarks: true, encoding: encoding ?? Encoding.UTF8, leaveOpen: true)) + { + return await reader.ReadToEndAsync(); + } } - /// - /// Creates a response for the the provided . - /// - /// The for this response. - /// The response status code. - /// The response data. - public static HttpResponseData CreateResponse(this HttpRequestData request, HttpStatusCode statusCode) - { - var response = request.CreateResponse(); - response.StatusCode = statusCode; - - return response; - } - } + /// + /// Reads the body payload as a string. + /// + /// The request from which to read. + /// The encoding to use when reading the string. Defaults to UTF-8 + /// A that represents request body. + public static string? ReadAsString(this HttpRequestData request, Encoding? encoding = null) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (request.Body is null) + { + return null; + } + + using (var reader = new StreamReader(request.Body, bufferSize: 1024, detectEncodingFromByteOrderMarks: true, encoding: encoding ?? Encoding.UTF8, leaveOpen: true)) + { + return reader.ReadToEnd(); + } + } + + /// + /// Reads the request using the default configured for this worker. + /// + /// The target type of the JSON value. + /// The request to be read. + /// A token that may be used to cancel the read operation. + /// A representing the asynchronous operation. + public static ValueTask ReadFromJsonAsync(this HttpRequestData request, CancellationToken cancellationToken = default) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + ObjectSerializer serializer = request.FunctionContext.InstanceServices.GetService>()?.Value?.Serializer + ?? throw new InvalidOperationException("A serializer is not configured for the worker."); + + return ReadFromJsonAsync(request, serializer, cancellationToken); + } + + /// + /// Reads the request using the provided . + /// + /// The target type of the JSON value. + /// The request to be read. + /// The to use for the deserialization. + /// A token that may be used to cancel the read operation. + /// A representing the asynchronous operation. + public static ValueTask ReadFromJsonAsync(this HttpRequestData request, ObjectSerializer serializer, CancellationToken cancellationToken = default) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (serializer is null) + { + throw new ArgumentNullException(nameof(serializer)); + } + + ValueTask result = serializer.DeserializeAsync(request.Body, typeof(T), cancellationToken); + + static T? TryCast(object? value) + { + return value != null + ? (T)value + : default; + } + + if (result.IsCompletedSuccessfully) + { + return new ValueTask(TryCast(result.Result)); + } + + return new ValueTask(result.AsTask().ContinueWith(t => TryCast(t.Result), cancellationToken)); + } + + /// + /// Creates a response for the provided . + /// + /// The for this response. + /// The response status code. + /// The response data. + public static HttpResponseData CreateResponse(this HttpRequestData request, HttpStatusCode statusCode) + { + var response = request.CreateResponse(); + response.StatusCode = statusCode; + + return response; + } + } } diff --git a/src/DotNetWorker.Core/Http/HttpResponseDataExtensions.cs b/src/DotNetWorker.Core/Http/HttpResponseDataExtensions.cs index e0f587559..e2689ecde 100644 --- a/src/DotNetWorker.Core/Http/HttpResponseDataExtensions.cs +++ b/src/DotNetWorker.Core/Http/HttpResponseDataExtensions.cs @@ -81,7 +81,7 @@ public static Task WriteStringAsync(this HttpResponseData response, string value /// /// Asynchronously writes the specified value as JSON to the response body using the default configured for this worker. - /// The response content-type will be set to application/json; charset=utf-8 and the status code set to 200. + /// The response content-type will be set to application/json; charset=utf-8. /// /// The type of object to write. /// The response to write JSON to. @@ -90,28 +90,12 @@ public static Task WriteStringAsync(this HttpResponseData response, string value /// A that represents the asynchronous operation. public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, CancellationToken cancellationToken = default) { - return WriteAsJsonAsync(response, instance, "application/json; charset=utf-8", HttpStatusCode.OK, cancellationToken); + return WriteAsJsonAsync(response, instance, "application/json; charset=utf-8", cancellationToken); } /// /// Asynchronously writes the specified value as JSON to the response body using the default configured for this worker. - /// The response content-type will be set to application/json; charset=utf-8 and the status code set to the provided . - /// - /// The type of object to write. - /// The response to write JSON to. - /// The instance to serialize and write as JSON. - /// The status code to set on the response. - /// A used to cancel the operation. - /// A that represents the asynchronous operation. - public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, HttpStatusCode statusCode, - CancellationToken cancellationToken = default) - { - return WriteAsJsonAsync(response, instance, "application/json; charset=utf-8", statusCode, cancellationToken); - } - - /// - /// Asynchronously writes the specified value as JSON to the response body using the default configured for this worker. - /// The response content-type will be set to the provided and the status code set to 200. + /// The response content-type will be set to the provided . /// /// The type of object to write. /// The response to write JSON to. @@ -128,37 +112,12 @@ public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T in ObjectSerializer serializer = GetObjectSerializer(response); - return WriteAsJsonAsync(response, instance, serializer, contentType, HttpStatusCode.OK, cancellationToken); - } - - /// - /// Asynchronously writes the specified value as JSON to the response body using the default configured for this worker. - /// The response content-type will be set to the provided and the status code set to the provided . - /// - /// The type of object to write. - /// The response to write JSON to. - /// The instance to serialize and write as JSON. - /// The content-type to set on the response. - /// The status code to set on the response. - /// A used to cancel the operation. - /// A that represents the asynchronous operation. - public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, string contentType, HttpStatusCode statusCode, - CancellationToken cancellationToken = default) - { - if (response is null) - { - throw new ArgumentNullException(nameof(response)); - } - - ObjectSerializer serializer = GetObjectSerializer(response); - - return WriteAsJsonAsync(response, instance, serializer, contentType, statusCode, cancellationToken); + return WriteAsJsonAsync(response, instance, serializer, contentType, cancellationToken); } - /// /// Asynchronously writes the specified value as JSON to the response body using the provided . - /// The response content-type will be set to application/json; charset=utf-8 and the status code set to 200. + /// The response content-type will be set to application/json; charset=utf-8. /// /// The type of object to write. /// The response to write JSON to. @@ -168,29 +127,12 @@ public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T in /// A that represents the asynchronous operation. public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, ObjectSerializer serializer, CancellationToken cancellationToken = default) { - return WriteAsJsonAsync(response, instance, serializer, "application/json; charset=utf-8", HttpStatusCode.OK, cancellationToken); + return WriteAsJsonAsync(response, instance, serializer, "application/json; charset=utf-8", cancellationToken); } - - /// - /// Asynchronously writes the specified value as JSON to the response body using the provided . - /// The response content-type will be set to application/json; charset=utf-8 and the status code set to the provided . - /// - /// The type of object to write. - /// The response to write JSON to. - /// The instance to serialize and write as JSON. - /// The serializer used to serialize the instance. - /// The status code to set on the response. - /// A used to cancel the operation. - /// A that represents the asynchronous operation. - public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, ObjectSerializer serializer, HttpStatusCode statusCode, - CancellationToken cancellationToken = default) - { - return WriteAsJsonAsync(response, instance, serializer, "application/json; charset=utf-8", statusCode, cancellationToken); - } - + /// /// Asynchronously writes the specified value as JSON to the response body using the provided . - /// The response content-type will be set to the provided and the status code set to 200. + /// The response content-type will be set to the provided . /// /// The type of object to write. /// The response to write JSON to. @@ -199,26 +141,7 @@ public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T in /// The content-type to set on the response. /// A used to cancel the operation. /// A that represents the asynchronous operation. - public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, - ObjectSerializer serializer, string contentType, - CancellationToken cancellationToken = default) - { - return WriteAsJsonAsync(response, instance, serializer, contentType, HttpStatusCode.OK, cancellationToken); - } - - /// - /// Asynchronously writes the specified value as JSON to the response body using the provided . - /// The response content-type will be set to the provided and the status code set to the provided . - /// - /// The type of object to write. - /// The response to write JSON to. - /// The instance to serialize and write as JSON. - /// The serializer used to serialize the instance. - /// The content-type to set on the response. - /// The status code to set on the response. - /// A used to cancel the operation. - /// A that represents the asynchronous operation. - public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, ObjectSerializer serializer, string contentType, HttpStatusCode statusCode, CancellationToken cancellationToken = default) + public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T instance, ObjectSerializer serializer, string contentType, CancellationToken cancellationToken = default) { if (response is null) { @@ -236,8 +159,6 @@ public static ValueTask WriteAsJsonAsync(this HttpResponseData response, T in } response.Headers.Add("Content-Type", contentType); - response.StatusCode = statusCode; - return serializer.SerializeAsync(response.Body, instance, typeof(T), cancellationToken); } diff --git a/src/DotNetWorker.Core/Http/IHttpRequestDataFeature.cs b/src/DotNetWorker.Core/Http/IHttpRequestDataFeature.cs index ab8e96a59..7742a2ade 100644 --- a/src/DotNetWorker.Core/Http/IHttpRequestDataFeature.cs +++ b/src/DotNetWorker.Core/Http/IHttpRequestDataFeature.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Functions.Worker.Http public interface IHttpRequestDataFeature { /// - /// Gets the instance if the FunctionContext contains an invocation for an http trigger. + /// Gets the instance if the FunctionContext contains an invocation for an HTTP trigger. /// /// The FunctionContext instance. /// HttpRequestData instance if the invocation is http, else null diff --git a/src/DotNetWorker.Core/Invocation/DefaultMethodInfoLocator.cs b/src/DotNetWorker.Core/Invocation/DefaultMethodInfoLocator.cs index a63a44f9e..8c9202d7c 100644 --- a/src/DotNetWorker.Core/Invocation/DefaultMethodInfoLocator.cs +++ b/src/DotNetWorker.Core/Invocation/DefaultMethodInfoLocator.cs @@ -1,16 +1,23 @@ using System; using System.Reflection; -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER using System.Runtime.Loader; #endif using System.Text.RegularExpressions; namespace Microsoft.Azure.Functions.Worker.Invocation { - internal class DefaultMethodInfoLocator : IMethodInfoLocator + internal partial class DefaultMethodInfoLocator : IMethodInfoLocator { - private static readonly Regex _entryPointRegex = new Regex("^(?.*)\\.(?\\S*)$"); + private const string EntryPointRegexPattern = "^(?.*)\\.(?\\S*)$"; +#if NET7_0_OR_GREATER + private static readonly Regex _entryPointRegex = EntryPointRegex(); + [GeneratedRegex(EntryPointRegexPattern)] + private static partial Regex EntryPointRegex(); +#else + private static readonly Regex _entryPointRegex = new(EntryPointRegexPattern); +#endif public MethodInfo GetMethod(string pathToAssembly, string entryPoint) { var entryPointMatch = _entryPointRegex.Match(entryPoint); @@ -21,7 +28,7 @@ public MethodInfo GetMethod(string pathToAssembly, string entryPoint) string typeName = entryPointMatch.Groups["typename"].Value; string methodName = entryPointMatch.Groups["methodname"].Value; -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(pathToAssembly); #else Assembly assembly = Assembly.LoadFrom(pathToAssembly); diff --git a/src/DotNetWorker.Core/Logging/ILoggerExtensions.cs b/src/DotNetWorker.Core/Logging/FunctionsLoggerExtensions.cs similarity index 94% rename from src/DotNetWorker.Core/Logging/ILoggerExtensions.cs rename to src/DotNetWorker.Core/Logging/FunctionsLoggerExtensions.cs index 0aaa9fb85..7dabf5ca4 100644 --- a/src/DotNetWorker.Core/Logging/ILoggerExtensions.cs +++ b/src/DotNetWorker.Core/Logging/FunctionsLoggerExtensions.cs @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Logging /// /// Extensions for . /// - public static class ILoggerExtensions + public static class FunctionsLoggerExtensions { /// /// Logs a metric value. Log will be at an information level. diff --git a/src/DotNetWorker.Core/Logging/ISystemLogWriter.cs b/src/DotNetWorker.Core/Logging/ISystemLogWriter.cs index 0f7b3eba4..4f6b6f16a 100644 --- a/src/DotNetWorker.Core/Logging/ISystemLogWriter.cs +++ b/src/DotNetWorker.Core/Logging/ISystemLogWriter.cs @@ -22,6 +22,6 @@ internal interface ISystemLogWriter /// The entry to be written. Can be also an object. /// The exception related to this entry. /// Function to create a message of the state and exception. - void WriteSystemLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter); + void WriteSystemLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter); } } diff --git a/src/DotNetWorker.Core/Logging/IUserLogWriter.cs b/src/DotNetWorker.Core/Logging/IUserLogWriter.cs index 79d0d0ef5..594fb626f 100644 --- a/src/DotNetWorker.Core/Logging/IUserLogWriter.cs +++ b/src/DotNetWorker.Core/Logging/IUserLogWriter.cs @@ -22,6 +22,6 @@ internal interface IUserLogWriter /// The entry to be written. Can be also an object. /// The exception related to this entry. /// Function to create a message of the state and exception. - void WriteUserLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter); + void WriteUserLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter); } } diff --git a/src/DotNetWorker.Core/Logging/NullLogWriter.cs b/src/DotNetWorker.Core/Logging/NullLogWriter.cs index bb08e2bb6..43b633cd6 100644 --- a/src/DotNetWorker.Core/Logging/NullLogWriter.cs +++ b/src/DotNetWorker.Core/Logging/NullLogWriter.cs @@ -22,12 +22,12 @@ private NullLogWriter() public static NullLogWriter Instance = new NullLogWriter(); /// - public void WriteSystemLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + public void WriteSystemLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { } /// - public void WriteUserLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + public void WriteUserLog(IExternalScopeProvider scopeProvider, string categoryName, LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { } diff --git a/src/DotNetWorker.Core/Logging/WorkerLogger.cs b/src/DotNetWorker.Core/Logging/WorkerLogger.cs index 324e58a86..7a9c1a22d 100644 --- a/src/DotNetWorker.Core/Logging/WorkerLogger.cs +++ b/src/DotNetWorker.Core/Logging/WorkerLogger.cs @@ -22,7 +22,8 @@ public WorkerLogger(string category, ISystemLogWriter systemLogWriter, IUserLogW _scopeProvider = scopeProvider; } - public IDisposable BeginScope(TState state) + public IDisposable? BeginScope(TState state) + where TState : notnull { // The built-in DI wire-up guarantees that scope provider will be set. return _scopeProvider.Push(state); @@ -33,7 +34,7 @@ public bool IsEnabled(LogLevel logLevel) return logLevel != LogLevel.None; } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (WorkerMessage.IsSystemLog) { diff --git a/src/DotNetWorker.Core/ModelBindingData.cs b/src/DotNetWorker.Core/ModelBindingData.cs index 8e4580f77..06b9122e3 100644 --- a/src/DotNetWorker.Core/ModelBindingData.cs +++ b/src/DotNetWorker.Core/ModelBindingData.cs @@ -6,17 +6,17 @@ namespace Microsoft.Azure.Functions.Worker.Core { /// - /// A representation of a Microsoft.Azure.WebJobs.ParameterBindingData + /// A representation of a Microsoft.Azure.WebJobs.ParameterBindingData. /// public abstract class ModelBindingData { /// - /// Gets the version of the binding data content + /// Gets the version of the binding data content. /// public abstract string Version { get; } /// - /// Gets the extension source of the binding data i.e CosmosDB, AzureStorageBlobs + /// Gets the extension source of the binding data (e.g., CosmosDB, AzureStorageBlobs). /// public abstract string Source { get; } @@ -26,7 +26,7 @@ public abstract class ModelBindingData public abstract BinaryData Content { get; } /// - /// Gets the content type of the binding data content i.e. "application/json" + /// Gets the content type of the binding data content (e.g., "application/json"). /// public abstract string ContentType { get; } } diff --git a/src/DotNetWorker.Core/Pipeline/FunctionExecutionMiddleware.cs b/src/DotNetWorker.Core/Pipeline/FunctionExecutionMiddleware.cs index 449c9afaa..cbde9e85d 100644 --- a/src/DotNetWorker.Core/Pipeline/FunctionExecutionMiddleware.cs +++ b/src/DotNetWorker.Core/Pipeline/FunctionExecutionMiddleware.cs @@ -1,22 +1,24 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Invocation; namespace Microsoft.Azure.Functions.Worker.Pipeline { - internal class FunctionExecutionMiddleware + internal class FunctionExecutionMiddleware(IFunctionExecutor functionExecutor) { - private readonly IFunctionExecutor _functionExecutor; - - public FunctionExecutionMiddleware(IFunctionExecutor functionExecutor) - { - _functionExecutor = functionExecutor; - } + private readonly IFunctionExecutor _functionExecutor = functionExecutor + ?? throw new ArgumentNullException(nameof(functionExecutor)); public Task Invoke(FunctionContext context) { + if (context.Features.Get() is { } executor) + { + return executor.ExecuteAsync(context).AsTask(); + } + return _functionExecutor.ExecuteAsync(context).AsTask(); } } diff --git a/src/DotNetWorker.Core/Properties/AssemblyInfo.cs b/src/DotNetWorker.Core/Properties/AssemblyInfo.cs index 3f9259f3e..b40f3e167 100644 --- a/src/DotNetWorker.Core/Properties/AssemblyInfo.cs +++ b/src/DotNetWorker.Core/Properties/AssemblyInfo.cs @@ -7,3 +7,5 @@ [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Grpc, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Tests.TestUtility, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/src/DotNetWorker.Core/StartupHook.cs b/src/DotNetWorker.Core/StartupHook.cs index 18fe2fd86..baea73d30 100644 --- a/src/DotNetWorker.Core/StartupHook.cs +++ b/src/DotNetWorker.Core/StartupHook.cs @@ -32,7 +32,7 @@ public static void Initialize() RemoveSelfFromStartupHooks(); string? debuggerWaitEnabled = Environment.GetEnvironmentVariable("FUNCTIONS_ENABLE_DEBUGGER_WAIT"); string? jsonOutputEnabled = Environment.GetEnvironmentVariable("FUNCTIONS_ENABLE_JSON_OUTPUT"); -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER int processId = Environment.ProcessId; #else int processId = Process.GetCurrentProcess().Id; @@ -57,12 +57,12 @@ static bool WaitOnDebugger(int cycle) if (string.Equals(jsonOutputEnabled, bool.TrueString, StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine($"azfuncjsonlog:{{ \"name\":\"dotnet-worker-startup\", \"workerProcessId\" : { processId } }}"); + Console.WriteLine($"azfuncjsonlog:{{ \"name\":\"dotnet-worker-startup\", \"workerProcessId\" : {processId} }}"); } if (string.Equals(debuggerWaitEnabled, bool.TrueString, StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine($"Azure Functions .NET Worker (PID: { processId }) initialized in debug mode. Waiting for debugger to attach..."); + Console.WriteLine($"Azure Functions .NET Worker (PID: {processId}) initialized in debug mode. Waiting for debugger to attach..."); for (int i = 0; WaitOnDebugger(i); i++) { diff --git a/src/DotNetWorker.Core/WorkerInformation.cs b/src/DotNetWorker.Core/WorkerInformation.cs index 843e10021..fc8b79285 100644 --- a/src/DotNetWorker.Core/WorkerInformation.cs +++ b/src/DotNetWorker.Core/WorkerInformation.cs @@ -20,7 +20,7 @@ internal sealed class WorkerInformation public static WorkerInformation Instance = new(); -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER public int ProcessId => Environment.ProcessId; public string RuntimeIdentifier => RuntimeInformation.RuntimeIdentifier; diff --git a/src/DotNetWorker.Grpc/Definition/GrpcFunctionDefinition.cs b/src/DotNetWorker.Grpc/Definition/GrpcFunctionDefinition.cs index efc55efa4..d5a9fd630 100644 --- a/src/DotNetWorker.Grpc/Definition/GrpcFunctionDefinition.cs +++ b/src/DotNetWorker.Grpc/Definition/GrpcFunctionDefinition.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Functions.Worker.Definition { - internal class GrpcFunctionDefinition : FunctionDefinition + internal sealed class GrpcFunctionDefinition : FunctionDefinition { private const string FunctionsWorkerDirectoryKey = "FUNCTIONS_WORKER_DIRECTORY"; private const string FunctionsApplicationDirectoryKey = "FUNCTIONS_APPLICATION_DIRECTORY"; @@ -42,9 +42,9 @@ public GrpcFunctionDefinition(FunctionLoadRequest loadRequest, IMethodInfoLocato string scriptFile = Path.Combine(scriptRoot, loadRequest.Metadata.ScriptFile); PathToAssembly = Path.GetFullPath(scriptFile); - var grpcBindingsGroup = loadRequest.Metadata.Bindings.GroupBy(kv => kv.Value.Direction); - var grpcInputBindings = grpcBindingsGroup.Where(kv => kv.Key == BindingInfo.Types.Direction.In).FirstOrDefault(); - var grpcOutputBindings = grpcBindingsGroup.Where(kv => kv.Key != BindingInfo.Types.Direction.In).FirstOrDefault(); + var grpcBindingsGroup = loadRequest.Metadata.Bindings.GroupBy(kv => kv.Value.Direction).ToArray(); + var grpcInputBindings = grpcBindingsGroup.FirstOrDefault(kv => kv.Key == BindingInfo.Types.Direction.In); + var grpcOutputBindings = grpcBindingsGroup.FirstOrDefault(kv => kv.Key != BindingInfo.Types.Direction.In); var infoToMetadataLambda = new Func, BindingMetadata>(kv => new GrpcBindingMetadata(kv.Key, kv.Value)); InputBindings = grpcInputBindings?.ToImmutableDictionary(kv => kv.Key, infoToMetadataLambda) diff --git a/src/DotNetWorker.Grpc/DotNetWorker.Grpc.csproj b/src/DotNetWorker.Grpc/DotNetWorker.Grpc.csproj index fd9d76056..77b177dd7 100644 --- a/src/DotNetWorker.Grpc/DotNetWorker.Grpc.csproj +++ b/src/DotNetWorker.Grpc/DotNetWorker.Grpc.csproj @@ -2,38 +2,32 @@ Library - net5.0;net6.0;net7.0;netstandard2.0 + net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0 Microsoft.Azure.Functions.Worker.Grpc This library provides gRPC support for Azure Functions .NET Worker communication with the Azure Functions Host. Microsoft.Azure.Functions.Worker.Grpc Microsoft.Azure.Functions.Worker.Grpc true - 17 + 2 + 51 0 - true - + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - - + + + + + - - - - - - diff --git a/test/DotNetWorkerTests/Executors/NullInstanceFactoryTests.cs b/test/DotNetWorkerTests/Executors/NullInstanceFactoryTests.cs index 7611f5951..0e1db23f4 100644 --- a/test/DotNetWorkerTests/Executors/NullInstanceFactoryTests.cs +++ b/test/DotNetWorkerTests/Executors/NullInstanceFactoryTests.cs @@ -4,8 +4,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.Azure.Functions.Worker.Tests diff --git a/test/DotNetWorkerTests/Features/DefaultInputConversionFeatureTests.cs b/test/DotNetWorkerTests/Features/DefaultInputConversionFeatureTests.cs index f71716975..646787b8e 100644 --- a/test/DotNetWorkerTests/Features/DefaultInputConversionFeatureTests.cs +++ b/test/DotNetWorkerTests/Features/DefaultInputConversionFeatureTests.cs @@ -10,7 +10,6 @@ using Azure.Core.Serialization; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Converters; -using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; using Xunit; diff --git a/test/DotNetWorkerTests/Features/TestFunctionBindingsFeature.cs b/test/DotNetWorkerTests/Features/TestFunctionBindingsFeature.cs deleted file mode 100644 index 9ba8218e3..000000000 --- a/test/DotNetWorkerTests/Features/TestFunctionBindingsFeature.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Microsoft.Azure.Functions.Worker.Context.Features; -using Microsoft.Azure.Functions.Worker.OutputBindings; - -namespace Microsoft.Azure.Functions.Worker.Tests.Features -{ - internal class TestFunctionBindingsFeature : IFunctionBindingsFeature - { - public IReadOnlyDictionary TriggerMetadata { get; init; } = new ReadOnlyDictionary(new Dictionary()); - - public IReadOnlyDictionary InputData { get; init; } = new ReadOnlyDictionary(new Dictionary()); - - public IDictionary OutputBindingData { get; } = new Dictionary(); - - public OutputBindingsInfo OutputBindingsInfo { get; init; } = EmptyOutputBindingsInfo.Instance; - - public object InvocationResult { get; set; } - } -} diff --git a/test/DotNetWorkerTests/FunctionContextExtensionTests.cs b/test/DotNetWorkerTests/FunctionContextExtensionTests.cs index 501d5e3a2..0326f9d98 100644 --- a/test/DotNetWorkerTests/FunctionContextExtensionTests.cs +++ b/test/DotNetWorkerTests/FunctionContextExtensionTests.cs @@ -11,7 +11,6 @@ using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Converters; using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Azure.Functions.Worker.Tests.Features; using Microsoft.Extensions.DependencyInjection; using Moq; using Newtonsoft.Json; diff --git a/test/DotNetWorkerTests/FunctionContextHttpExtensionTests.cs b/test/DotNetWorkerTests/FunctionContextHttpExtensionTests.cs index 2778da067..e2d7fc825 100644 --- a/test/DotNetWorkerTests/FunctionContextHttpExtensionTests.cs +++ b/test/DotNetWorkerTests/FunctionContextHttpExtensionTests.cs @@ -10,9 +10,7 @@ using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Converters; -using Microsoft.Azure.Functions.Worker.Grpc.Messages; using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Azure.Functions.Worker.Tests.Features; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; diff --git a/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataManagerTests.cs b/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataManagerTests.cs new file mode 100644 index 000000000..04d10c6cb --- /dev/null +++ b/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataManagerTests.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +#nullable enable + +namespace Microsoft.Azure.Functions.Worker.Tests.FunctionMetadata +{ + public class DefaultFunctionMetadataManagerTests + { + [Fact] + public async Task GetFunctionMetadataAsync_ReturnsTransformedMetadata() + { + var mockProvider = new Mock(MockBehavior.Strict); + var mockTransformer = new Mock(MockBehavior.Strict); + var mockLogger = new Mock>(); + + var metadata = new List { new TestFunctionMetadata() }.ToImmutableArray(); + mockProvider.Setup(p => p.GetFunctionMetadataAsync(It.IsAny())).ReturnsAsync(metadata); + + mockTransformer.SetupGet(t => t.Name).Returns("TestTransformer"); + mockTransformer.Setup(t => t.Transform(It.IsAny>())); + + var manager = new DefaultFunctionMetadataManager( + mockProvider.Object, + new[] { mockTransformer.Object }, + mockLogger.Object); + + var result = await manager.GetFunctionMetadataAsync("test"); + + Assert.Single(result); + mockProvider.Verify(p => p.GetFunctionMetadataAsync("test"), Times.Once); + mockTransformer.Verify(t => t.Transform(It.IsAny>()), Times.Once); + } + + [Fact] + public async Task GetFunctionMetadataAsync_NoTransformers_ReturnsOriginalMetadata() + { + var mockProvider = new Mock(MockBehavior.Strict); + var mockLogger = new Mock>(); + var metadata = new List { new TestFunctionMetadata() }.ToImmutableArray(); + mockProvider.Setup(p => p.GetFunctionMetadataAsync(It.IsAny())).ReturnsAsync(metadata); + + var manager = new DefaultFunctionMetadataManager( + mockProvider.Object, + Array.Empty(), + mockLogger.Object); + + var result = await manager.GetFunctionMetadataAsync("test"); + + Assert.Single(result); + mockProvider.Verify(p => p.GetFunctionMetadataAsync("test"), Times.Once); + } + + [Fact] + public async Task GetFunctionMetadataAsync_TransformerThrows_LogsAndThrows() + { + var mockProvider = new Mock(MockBehavior.Strict); + var mockTransformer = new Mock(MockBehavior.Strict); + var mockLogger = new Mock>(); + var metadata = ImmutableArray.Empty; + + mockProvider.Setup(p => p.GetFunctionMetadataAsync(It.IsAny())).ReturnsAsync(metadata); + mockTransformer.SetupGet(t => t.Name).Returns("ThrowingTransformer"); + mockTransformer.Setup(t => t.Transform(It.IsAny>())) + .Throws(new InvalidOperationException("fail")); + + var manager = new DefaultFunctionMetadataManager( + mockProvider.Object, + [mockTransformer.Object], + mockLogger.Object); + + var ex = await Assert.ThrowsAsync(() => manager.GetFunctionMetadataAsync("test")); + Assert.Equal("fail", ex.Message); + mockLogger.Verify(l => l.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => v.ToString()!.Contains("ThrowingTransformer")), + It.IsAny(), + It.IsAny>()), Times.Once); + } + + private class TestFunctionMetadata : IFunctionMetadata + { + public string? FunctionId => "id"; + public bool IsProxy => false; + public string? Language => "dotnet"; + public bool ManagedDependencyEnabled => false; + public string? Name => "Test"; + public string? EntryPoint => "Test.Run"; + public IList? RawBindings => new List(); + public string? ScriptFile => "Test.dll"; + public IRetryOptions? Retry => null; + } + } +} diff --git a/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataTests.cs b/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataTests.cs index 13875a70b..4e00f6be7 100644 --- a/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataTests.cs +++ b/test/DotNetWorkerTests/FunctionMetadata/DefaultFunctionMetadataTests.cs @@ -1,11 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; using Xunit; -namespace DotNetWorkerTests.FunctionMetadata +namespace Microsoft.Azure.Functions.Worker.Tests.FunctionMetadata { public class DefaultFunctionMetadataTests { diff --git a/test/DotNetWorkerTests/FunctionsApplicationTests.cs b/test/DotNetWorkerTests/FunctionsApplicationTests.cs index 05c84c669..eb5149e4e 100644 --- a/test/DotNetWorkerTests/FunctionsApplicationTests.cs +++ b/test/DotNetWorkerTests/FunctionsApplicationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; @@ -114,16 +114,16 @@ private static FunctionsApplication CreateApplication(FunctionExecutionDelegate var options = new OptionsWrapper(new WorkerOptions()); var contextFactory = new Mock(); var diagnostics = new Mock(); - var activityFactory = new FunctionActivitySourceFactory(new OptionsWrapper(new WorkerOptions())); + var telemetryProvider = new TelemetryProviderV1_17_0(); - return new FunctionsApplication(invoke, contextFactory.Object, options, logger, diagnostics.Object, activityFactory); + return new FunctionsApplication(invoke, contextFactory.Object, options, logger, diagnostics.Object, telemetryProvider); } private static ActivityListener CreateListener(Action onStopped) { var listener = new ActivityListener { - ShouldListenTo = source => source.Name.StartsWith(TraceConstants.FunctionsActivitySource), + ShouldListenTo = source => source.Name.StartsWith(TraceConstants.ActivityAttributes.Name), ActivityStarted = activity => { }, ActivityStopped = onStopped, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, diff --git a/test/DotNetWorkerTests/GrpcFunctionDefinitionTests.cs b/test/DotNetWorkerTests/GrpcFunctionDefinitionTests.cs index 4ca86017b..dcbb124b1 100644 --- a/test/DotNetWorkerTests/GrpcFunctionDefinitionTests.cs +++ b/test/DotNetWorkerTests/GrpcFunctionDefinitionTests.cs @@ -124,7 +124,7 @@ public void GrpcFunctionDefinition_BlobInput_Creates() Assert.Equal(typeof(string), q.Type); Assert.Contains(PropertyBagKeys.ConverterFallbackBehavior, q.Properties.Keys); Assert.Contains(PropertyBagKeys.BindingAttributeSupportedConverters, q.Properties.Keys); - Assert.Equal("Default", q.Properties[PropertyBagKeys.ConverterFallbackBehavior].ToString()); + Assert.Equal("Allow", q.Properties[PropertyBagKeys.ConverterFallbackBehavior].ToString()); Assert.Contains(new Dictionary>().ToString(), q.Properties[PropertyBagKeys.BindingAttributeSupportedConverters].ToString()); }); @@ -182,7 +182,7 @@ public void GrpcFunctionDefinition_QueueTrigger_Creates() Assert.Equal(typeof(QueueMessage), q.Type); Assert.Contains(PropertyBagKeys.ConverterFallbackBehavior, q.Properties.Keys); Assert.Contains(PropertyBagKeys.BindingAttributeSupportedConverters, q.Properties.Keys); - Assert.Equal("Default", q.Properties[PropertyBagKeys.ConverterFallbackBehavior].ToString()); + Assert.Equal("Allow", q.Properties[PropertyBagKeys.ConverterFallbackBehavior].ToString()); Assert.Contains(new Dictionary>().ToString(), q.Properties[PropertyBagKeys.BindingAttributeSupportedConverters].ToString()); }); } @@ -231,7 +231,7 @@ public void GrpcFunctionDefinition_TableInput_Creates() Assert.Equal(typeof(TableClient), q.Type); Assert.Contains(PropertyBagKeys.ConverterFallbackBehavior, q.Properties.Keys); Assert.Contains(PropertyBagKeys.BindingAttributeSupportedConverters, q.Properties.Keys); - Assert.Equal("Default", q.Properties[PropertyBagKeys.ConverterFallbackBehavior].ToString()); + Assert.Equal("Allow", q.Properties[PropertyBagKeys.ConverterFallbackBehavior].ToString()); Assert.Contains(new Dictionary>().ToString(), q.Properties[PropertyBagKeys.BindingAttributeSupportedConverters].ToString()); }); diff --git a/test/DotNetWorkerTests/GrpcWorkerTests.cs b/test/DotNetWorkerTests/GrpcWorkerTests.cs index a19a4a8fe..01c722eb5 100644 --- a/test/DotNetWorkerTests/GrpcWorkerTests.cs +++ b/test/DotNetWorkerTests/GrpcWorkerTests.cs @@ -9,7 +9,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Azure.Core.Serialization; using Microsoft.Azure.Functions.Tests; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; @@ -80,6 +79,19 @@ public void LoadFunction_ReturnsSuccess() Assert.Equal(StatusResult.Types.Status.Success, response.Result.Status); } + [Theory] + [InlineData(".NET Core 3.1.1", ".NET Core")] + [InlineData(".NET 8.0.0", ".NET")] + [InlineData(".NET Framework 4.8.4250.0", ".NET Framework")] + [InlineData(".NET Native", ".NET Native")] + [InlineData(".NET Native 1.0.0", ".NET Native")] + [InlineData("Mono 5.18.1.0", "Mono")] + public void GetWorkerMetadata_ParsesFrameworkDescription(string frameworkDescription, string expectedFramework) + { + var workerMetadata = GrpcWorker.GetWorkerMetadata(frameworkDescription); + Assert.Equal(expectedFramework, workerMetadata.RuntimeName); + } + [Fact] public void LoadFunction_WithProxyMetadata_ReturnsSuccess() { @@ -178,6 +190,8 @@ public void InitRequest_ReturnsExpectedMetadata() [Theory] [InlineData("IncludeEmptyEntriesInMessagePayload", true, "IncludeEmptyEntriesInMessagePayload", true, "True")] [InlineData("IncludeEmptyEntriesInMessagePayload", false, "IncludeEmptyEntriesInMessagePayload", false)] + [InlineData("EnableUserCodeException", true, "EnableUserCodeException", true, "True")] + [InlineData("EnableUserCodeException", false, "EnableUserCodeException", false)] public void InitRequest_ReturnsExpectedCapabilities_BasedOnWorkerOptions( string booleanPropertyName, bool booleanPropertyValue, @@ -233,7 +247,9 @@ void AssertKeyAndValue(KeyValuePair kvp, string expectedKey, str } Assert.Collection(response.Capabilities.OrderBy(p => p.Key), + c => AssertKeyAndValue(c, "EnableUserCodeException", bool.TrueString), c => AssertKeyAndValue(c, "HandlesInvocationCancelMessage", bool.TrueString), + c => AssertKeyAndValue(c, "IncludeEmptyEntriesInMessagePayload", bool.TrueString), c => AssertKeyAndValue(c, "RawHttpBodyBytes", bool.TrueString), c => AssertKeyAndValue(c, "RpcHttpBodyOnly", bool.TrueString), c => AssertKeyAndValue(c, "RpcHttpTriggerMetadataRemoved", bool.TrueString), @@ -263,7 +279,7 @@ public async Task Invocation_WhenSynchronous_DoesNotBlock() var clientFactoryMock = new Mock(); var clientMock = new Mock(); - var metadataProvider = new Mock(); + var metadataManager = new Mock(); var invocationHandlerMock = new Mock(); InvocationResponse ValueFunction(InvocationRequest request) @@ -294,7 +310,7 @@ InvocationResponse ValueFunction(InvocationRequest request) clientFactoryMock.Object, _mockMethodInfoLocator.Object, new OptionsWrapper(new WorkerOptions()), - metadataProvider.Object, + metadataManager.Object, new ApplicationLifetime(TestLogger.Create()), invocationHandlerMock.Object); @@ -324,7 +340,7 @@ void ProcessMessage(IMessageProcessor processor, string functionId = null) }); releaseFunctionEvent.Wait(5000); - + Assert.True(releaseFunctionEvent.IsSet, "Release function was never called. " + "This indicates the blocking function prevented execution flow."); diff --git a/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs b/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs index 95363894d..c7e37dec2 100644 --- a/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs +++ b/test/DotNetWorkerTests/Handlers/InvocationHandlerTests.cs @@ -77,14 +77,15 @@ public async Task InvokeAsync_ThrowsTaskCanceledException_ReturnsCancelled() { _mockApplication .Setup(m => m.InvokeFunctionAsync(It.IsAny())) - .Throws(new AggregateException(new Exception[] { new TaskCanceledException() })); + .Throws(new AggregateException(new TaskCanceledException())); var request = TestUtility.CreateInvocationRequest("abc"); var invocationHandler = CreateInvocationHandler(); var response = await invocationHandler.InvokeAsync(request); Assert.Equal(StatusResult.Types.Status.Cancelled, response.Result.Status); - Assert.Contains("TaskCanceledException", response.Result.Exception.Message); + Assert.Equal("System.AggregateException", response.Result.Exception.Type); + Assert.Contains("A task was canceled", response.Result.Exception.Message); } [Fact] @@ -129,15 +130,16 @@ public async Task Cancel_InvocationCompleted_ReturnsFalse() } /// - /// Test unwrapping user code exception functionality. + /// Test unwrapping user code exception functionality. + /// EnableUserCodeException capability is true by default. /// [Fact] public async Task InvokeAsync_UserCodeThrowsException_OptionEnabled() { var exceptionMessage = "user code exception"; + var mockOptions = new OptionsWrapper(new() { - EnableUserCodeException = true, Serializer = new JsonObjectSerializer() }); @@ -156,23 +158,26 @@ public async Task InvokeAsync_UserCodeThrowsException_OptionEnabled() } /// - /// Test keeping user code exception wrapped as RpcException. + /// Test keeping user code exception wrapped as RpcException. /// [Fact] public async Task InvokeAsync_UserCodeThrowsException_OptionDisabled() { var exceptionMessage = "user code exception"; - var mockOptions = new WorkerOptions() +#pragma warning disable CS0618 // Type or member is obsolete. Test obsolete property. + var mockOptions = new OptionsWrapper(new() { + Serializer = new JsonObjectSerializer(), EnableUserCodeException = false - }; + }); +#pragma warning restore CS0618 // Type or member is obsolete _mockApplication .Setup(m => m.InvokeFunctionAsync(It.IsAny())) .Throws(new Exception(exceptionMessage)); var request = TestUtility.CreateInvocationRequest("abc"); - var invocationHandler = CreateInvocationHandler(); + var invocationHandler = CreateInvocationHandler(workerOptions: mockOptions); var response = await invocationHandler.InvokeAsync(request); Assert.Equal(StatusResult.Types.Status.Failure, response.Result.Status); @@ -239,8 +244,9 @@ public async Task Invoke_CreateContextThrows_ReturnsFailure() var response = await invocationHandler.InvokeAsync(request); Assert.Equal(StatusResult.Types.Status.Failure, response.Result.Status); - Assert.Contains("InvalidOperationException: whoops", response.Result.Exception.Message); - Assert.Contains("CreateContext", response.Result.Exception.Message); + Assert.Equal("System.InvalidOperationException", response.Result.Exception.Type); + Assert.Contains("whoops", response.Result.Exception.Message); + Assert.Contains("CreateContext", response.Result.Exception.StackTrace); } [Fact] diff --git a/test/DotNetWorkerTests/Helpers/TestLoggerT.cs b/test/DotNetWorkerTests/Helpers/TestLoggerT.cs index 2529a8c70..a2527cc9d 100644 --- a/test/DotNetWorkerTests/Helpers/TestLoggerT.cs +++ b/test/DotNetWorkerTests/Helpers/TestLoggerT.cs @@ -16,10 +16,10 @@ private TestLogger(string category) public static TestLogger Create() { // We want to use the logic for category naming which is internal to LoggerFactory. - // So we'll create a TestLogger via the LoggerFactory and grab it's category. + // So we'll create a TestLogger via the LoggerFactory and grab its category. TestLoggerProvider testLoggerProvider = new TestLoggerProvider(); - LoggerFactory _testLoggerFactory = new LoggerFactory(new[] { testLoggerProvider }); - _testLoggerFactory.CreateLogger(); + LoggerFactory testLoggerFactory = new LoggerFactory([testLoggerProvider]); + testLoggerFactory.CreateLogger(); TestLogger testLogger = testLoggerProvider.CreatedLoggers.Single(); return new TestLogger(testLogger.Category); } diff --git a/test/DotNetWorkerTests/Http/HttpResponseDataExtensionsTests.cs b/test/DotNetWorkerTests/Http/HttpResponseDataExtensionsTests.cs index 09b51af49..a2bc312d3 100644 --- a/test/DotNetWorkerTests/Http/HttpResponseDataExtensionsTests.cs +++ b/test/DotNetWorkerTests/Http/HttpResponseDataExtensionsTests.cs @@ -1,13 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Runtime.InteropServices; -using System.Text; using System.Text.Json.Serialization; using System.Threading.Tasks; using Azure.Core.Serialization; @@ -68,7 +64,7 @@ public async Task WriteAsJsonAsync_UsesRegisteredSerializer() public async Task WriteAsJsonAsync_ContentTypeOverload_AppliesParameters() { FunctionContext context = CreateContext(new NewtonsoftJsonObjectSerializer()); - var response = CreateResponse(context); + var response = CreateResponse(context, HttpStatusCode.Accepted); var poco = new ResponsePoco { @@ -81,7 +77,7 @@ public async Task WriteAsJsonAsync_ContentTypeOverload_AppliesParameters() string result = ReadResponseBody(response); Assert.Equal("application/json", response.Headers.GetValues("content-type").FirstOrDefault()); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); Assert.Equal("{\"jsonnetname\":\"Test\",\"jsonnetint\":42}", result); } @@ -89,7 +85,7 @@ public async Task WriteAsJsonAsync_ContentTypeOverload_AppliesParameters() public async Task WriteAsJsonAsync_StatusCodeOverload_AppliesParameters() { FunctionContext context = CreateContext(new NewtonsoftJsonObjectSerializer()); - var response = CreateResponse(context); + var response = CreateResponse(context, HttpStatusCode.BadRequest); var poco = new ResponsePoco { @@ -97,7 +93,7 @@ public async Task WriteAsJsonAsync_StatusCodeOverload_AppliesParameters() SomeInt = 42 }; - await HttpResponseDataExtensions.WriteAsJsonAsync(response, poco, HttpStatusCode.BadRequest); + await HttpResponseDataExtensions.WriteAsJsonAsync(response, poco); string result = ReadResponseBody(response); @@ -127,9 +123,9 @@ public async Task WriteAsJsonAsync_SerializerAndContentTypeOverload_AppliesParam Assert.Equal("{\"jsonnetname\":\"Test\",\"jsonnetint\":42}", result); } - private static TestHttpResponseData CreateResponse(FunctionContext context) + private static TestHttpResponseData CreateResponse(FunctionContext context, HttpStatusCode statusCode = HttpStatusCode.OK) { - var response = new TestHttpResponseData(context, HttpStatusCode.Accepted); + var response = new TestHttpResponseData(context, statusCode); response.Body = new MemoryStream(); response.Headers = new HttpHeadersCollection(); return response; diff --git a/test/DotNetWorkerTests/Pipelines/FunctionsExecutionMiddlewareTests.cs b/test/DotNetWorkerTests/Pipelines/FunctionsExecutionMiddlewareTests.cs new file mode 100644 index 000000000..11e8b3ba0 --- /dev/null +++ b/test/DotNetWorkerTests/Pipelines/FunctionsExecutionMiddlewareTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker.Invocation; +using Moq; +using Xunit; + +namespace Microsoft.Azure.Functions.Worker.Pipeline.Tests +{ + public class FunctionsExecutionMiddlewareTests + { + [Fact] + public void Ctor_ThrowsArgumentNullException_WhenFunctionExecutorIsNull() + { + // Arrange & Act & Assert + var exception = Assert.Throws(() => new FunctionExecutionMiddleware(null!)); + Assert.Equal("functionExecutor", exception.ParamName); + } + + [Fact] + public async Task Invoke_NoFeature_CallsInjectedExecutor() + { + // Arrange + Mock functionExecutorMock = new(MockBehavior.Strict); + FunctionExecutionMiddleware middleware = new(functionExecutorMock.Object); + Mock functionContextMock = new(MockBehavior.Strict); + functionContextMock.Setup(m => m.Features).Returns(new InvocationFeatures([])); + + functionExecutorMock + .Setup(m => m.ExecuteAsync(functionContextMock.Object)) + .Returns(ValueTask.CompletedTask) + .Verifiable(); + + // Act + await middleware.Invoke(functionContextMock.Object); + + // Assert + functionExecutorMock.Verify(f => f.ExecuteAsync(functionContextMock.Object), Times.Once); + } + + [Fact] + public async Task Invoke_Feature_CallsFeatureExecutor() + { + // Arrange + Mock functionExecutorMock1 = new(MockBehavior.Strict); + Mock functionExecutorMock2 = new(MockBehavior.Strict); + FunctionExecutionMiddleware middleware = new(functionExecutorMock1.Object); + Mock functionContextMock = new(MockBehavior.Strict); + + InvocationFeatures features = new([]); + features.Set(functionExecutorMock2.Object); + functionContextMock.Setup(m => m.Features).Returns(features); + + functionExecutorMock2 + .Setup(m => m.ExecuteAsync(functionContextMock.Object)) + .Returns(ValueTask.CompletedTask) + .Verifiable(); + + // Act + await middleware.Invoke(functionContextMock.Object); + + // Assert + functionExecutorMock1.Verify(f => f.ExecuteAsync(It.IsAny()), Times.Never); + functionExecutorMock2.Verify(f => f.ExecuteAsync(functionContextMock.Object), Times.Once); + } + } +} diff --git a/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs b/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs index 50038ed60..980a5f097 100644 --- a/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs +++ b/test/E2ETests/E2EApps/E2EApp/Cosmos/CosmosFunction.cs @@ -127,6 +127,11 @@ public async Task DocByIdFromRouteData( Id = "{id}", PartitionKey = "{partitionKey}")] MyDocument doc) { + if (doc == null) + { + return req.CreateResponse(HttpStatusCode.NotFound); + } + var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteStringAsync(doc.Text); return response; diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index 7269347c3..3409c286f 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -25,7 +25,6 @@ - @@ -45,9 +44,9 @@ - - - - + + + + \ No newline at end of file diff --git a/test/E2ETests/E2EApps/E2EApp/Table/TableInputBindingFunctions.cs b/test/E2ETests/E2EApps/E2EApp/Table/TableInputBindingFunctions.cs index 58db63c9f..f9437c76f 100644 --- a/test/E2ETests/E2EApps/E2EApp/Table/TableInputBindingFunctions.cs +++ b/test/E2ETests/E2EApps/E2EApp/Table/TableInputBindingFunctions.cs @@ -1,9 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using Azure; using Azure.Data.Tables; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -87,7 +89,7 @@ public async Task EnumerableFunction( return response; } - [Function("DoesNotSupportDeferredBinding")] + [Function("PocoFunction")] public static void TableInput( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, [TableInput("TestTable", "MyPartition", "yo")] MyPoco poco, @@ -96,11 +98,53 @@ public static void TableInput( log.LogInformation($"PK={poco.PartitionKey}, RK={poco.RowKey}, Text={poco.Text}"); } + [Function("PocoEnumerableFunction")] + public static void TableInput( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, + [TableInput("TestTable", "MyPartition")] IEnumerable pocoList, + ILogger log) + { + foreach (MyPoco poco in pocoList) + { + log.LogInformation($"PK={poco.PartitionKey}, Text={poco.Text}"); + } + } + + [Function("MyEntityFunction")] + public static void TableInput( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, + [TableInput("TestTable", "MyPartition", "yo")] MyEntity poco, + ILogger log) + { + log.LogInformation($"PK={poco.PartitionKey}, RK={poco.RowKey}, Text={poco.Text}"); + } + + [Function("MyEntityEnumerableFunction")] + public static void TableInput( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, + [TableInput("TestTable", "MyPartition")] IEnumerable pocoList, + ILogger log) + { + foreach (MyEntity poco in pocoList) + { + log.LogInformation($"PK={poco.PartitionKey}, Text={poco.Text}"); + } + } + public class MyPoco { public string PartitionKey { get; set; } public string RowKey { get; set; } public string Text { get; set; } } + + public class MyEntity : ITableEntity + { + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public string Text { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } + } } } diff --git a/test/E2ETests/E2EApps/E2EApp/local.settings.json b/test/E2ETests/E2EApps/E2EApp/local.settings.json index 3b70ea809..c7812bf34 100644 --- a/test/E2ETests/E2EApps/E2EApp/local.settings.json +++ b/test/E2ETests/E2EApps/E2EApp/local.settings.json @@ -1,5 +1,5 @@ { - "IsEncrypted": false, + "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", diff --git a/test/E2ETests/E2EApps/E2EAspNetCoreApp/CancellationHttpFunctions.cs b/test/E2ETests/E2EApps/E2EAspNetCoreApp/CancellationHttpFunctions.cs new file mode 100644 index 000000000..bf96dd01d --- /dev/null +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/CancellationHttpFunctions.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; + +namespace Microsoft.Azure.Functions.Worker.E2EApp +{ + public class CancellationHttpFunctions(ILogger logger) + { + private readonly ILogger _logger = logger; + + [Function(nameof(HttpWithCancellationTokenNotUsed))] + public async Task HttpWithCancellationTokenNotUsed( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req) + { + _logger.LogInformation("HttpWithCancellationTokenNotUsed processed a request."); + + await SimulateWork(CancellationToken.None); + + return new OkObjectResult("Processing completed successfully."); + } + + [Function(nameof(HttpWithCancellationTokenIgnored))] + public async Task HttpWithCancellationTokenIgnored( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req, + CancellationToken cancellationToken) + { + _logger.LogInformation("HttpWithCancellationTokenIgnored processed a request."); + + await SimulateWork(cancellationToken); + + return new OkObjectResult("Processing completed successfully."); + } + + [Function(nameof(HttpWithCancellationTokenHandled))] + public async Task HttpWithCancellationTokenHandled( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, + CancellationToken cancellationToken) + { + _logger.LogInformation("HttpWithCancellationTokenHandled processed a request."); + + try + { + await SimulateWork(cancellationToken); + + return new OkObjectResult("Processing completed successfully."); + } + catch (OperationCanceledException) + { + _logger.LogWarning("Request was cancelled."); + + // Take precautions like noting how far along you are with processing the batch + await Task.Delay(1000); + + return new ObjectResult(new { statusCode = StatusCodes.Status499ClientClosedRequest, message = "Request was cancelled." }); + } + } + + private async Task SimulateWork(CancellationToken cancellationToken) + { + _logger.LogInformation("Starting work..."); + + for (int i = 0; i < 5; i++) + { + // Simulate work + await Task.Delay(1000, cancellationToken); + _logger.LogWarning($"Work iteration {i + 1} completed."); + } + + _logger.LogInformation("Work completed."); + } + } +} diff --git a/test/E2ETests/E2EApps/E2EAspNetCoreApp/E2EAspNetCoreApp.csproj b/test/E2ETests/E2EApps/E2EAspNetCoreApp/E2EAspNetCoreApp.csproj new file mode 100644 index 000000000..5c2fd1e8c --- /dev/null +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/E2EAspNetCoreApp.csproj @@ -0,0 +1,33 @@ + + + net8.0 + v4 + Exe + enable + enable + Microsoft.Azure.Functions.Worker.E2EAspNetCoreApp + Microsoft.Azure.Functions.Worker.E2EAspNetCoreApp + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + + + + + \ No newline at end of file diff --git a/test/E2ETests/E2EApps/E2EAspNetCoreApp/NuGet.Config b/test/E2ETests/E2EApps/E2EAspNetCoreApp/NuGet.Config new file mode 100644 index 000000000..2a9de739f --- /dev/null +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/E2ETests/E2EApps/E2EAspNetCoreApp/Program.cs b/test/E2ETests/E2EApps/E2EAspNetCoreApp/Program.cs new file mode 100644 index 000000000..e8cfbc5ba --- /dev/null +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/Program.cs @@ -0,0 +1,9 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; + +var host = new HostBuilder() + .ConfigureFunctionsWebApplication() + .Build(); + +host.Run(); diff --git a/samples/Net7Worker/Properties/launchSettings.json b/test/E2ETests/E2EApps/E2EAspNetCoreApp/Properties/launchSettings.json similarity index 55% rename from samples/Net7Worker/Properties/launchSettings.json rename to test/E2ETests/E2EApps/E2EAspNetCoreApp/Properties/launchSettings.json index a0bd94b09..0c6540c91 100644 --- a/samples/Net7Worker/Properties/launchSettings.json +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/Properties/launchSettings.json @@ -1,9 +1,9 @@ -{ - "profiles": { - "Net7Worker": { - "commandName": "Project", - "commandLineArgs": "--port 7199", - "launchBrowser": false - } - } +{ + "profiles": { + "dni_net8_cancellation": { + "commandName": "Project", + "commandLineArgs": "--port 7097", + "launchBrowser": false + } + } } \ No newline at end of file diff --git a/test/E2ETests/E2EApps/E2EAspNetCoreApp/host.json b/test/E2ETests/E2EApps/E2EAspNetCoreApp/host.json new file mode 100644 index 000000000..ee5cf5f83 --- /dev/null +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file diff --git a/test/E2ETests/E2EApps/E2EAspNetCoreApp/local.settings.json b/test/E2ETests/E2EApps/E2EAspNetCoreApp/local.settings.json new file mode 100644 index 000000000..aa72b19ab --- /dev/null +++ b/test/E2ETests/E2EApps/E2EAspNetCoreApp/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" + } +} \ No newline at end of file diff --git a/test/E2ETests/E2ETests/AspNetCore/CancellationEndToEndTests.cs b/test/E2ETests/E2ETests/AspNetCore/CancellationEndToEndTests.cs new file mode 100644 index 000000000..4bfce6034 --- /dev/null +++ b/test/E2ETests/E2ETests/AspNetCore/CancellationEndToEndTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Tests; +using Microsoft.Azure.Functions.Tests.E2ETests; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Azure.Functions.Worker.E2ETests.AspNetCore +{ + public class CancellationEndToEndTests : IClassFixture + { + private readonly TestFixture _fixture; + + public CancellationEndToEndTests(TestFixture fixture, ITestOutputHelper testOutputHelper) + { + _fixture = fixture; + _fixture.TestLogs.UseTestLogger(testOutputHelper); + } + + [IgnoreOnNetFxTestRunTheory] + [InlineData("HttpWithCancellationTokenNotUsed", "Work completed.", "Succeeded")] + [InlineData("HttpWithCancellationTokenIgnored", "TaskCanceledException: A task was canceled", "Failed")] + [InlineData("HttpWithCancellationTokenHandled", "Request was cancelled.", "Succeeded")] + public async Task HttpTriggerFunctions_WithCancellationToken_BehaveAsExpected(string functionName, string expectedMessage, string invocationResult) + { + using var cts = new CancellationTokenSource(); + var task = HttpHelpers.InvokeHttpTrigger(functionName, cancellationToken: cts.Token); + + // Make sure the function invocation started before we cancel + IEnumerable invocationStartLog = null; + await TestUtility.RetryAsync(() => + { + invocationStartLog = _fixture.TestLogs.CoreToolsLogs.Where(p => p.Contains($"Executing 'Functions.{functionName}'")); + return Task.FromResult(invocationStartLog.Count() >= 1); + }); + + // The task should be cancelled before it completes, mimicing a client closing the connection. + // This should lead to the worker getting an InvocationCancel request from the functions host + cts.Cancel(); + await Assert.ThrowsAsync(async () => await task); + + IEnumerable invocationEndLog = null; + await TestUtility.RetryAsync(() => + { + invocationEndLog = _fixture.TestLogs.CoreToolsLogs.Where(p => p.Contains($"Executed 'Functions.{functionName}'")); + return Task.FromResult(invocationEndLog.Count() >= 1); + }); + + Assert.Contains(_fixture.TestLogs.CoreToolsLogs, log => log.Contains(expectedMessage, StringComparison.OrdinalIgnoreCase)); + + // TODO: 2/3 of the test invocations will fail until the host with the ForwarderProxy fix is released - uncomment this line when the fix is released + Assert.NotEqual(null, invocationResult); // just here to 'use' invocationResult to avoid a warning. + // Assert.Contains(_fixture.TestLogs.CoreToolsLogs, log => log.Contains($"'Functions.{functionName}' ({invocationResult}", StringComparison.OrdinalIgnoreCase)); + } + + public class TestFixture : FunctionAppFixture + { + public TestFixture(IMessageSink messageSink) : base(messageSink, Constants.TestAppNames.E2EAspNetCoreApp) + { + } + } + } +} diff --git a/test/E2ETests/E2ETests/Constants.cs b/test/E2ETests/E2ETests/Constants.cs index 97cf15589..e8d554ab4 100644 --- a/test/E2ETests/E2ETests/Constants.cs +++ b/test/E2ETests/E2ETests/Constants.cs @@ -98,5 +98,11 @@ public static class Tables public const string TablesConnectionStringSetting = EmulatorConnectionString; public const string TableName = "TestTable"; } + + public static class TestAppNames + { + public const string E2EApp = "E2EApp"; + public const string E2EAspNetCoreApp = "E2EAspNetCoreApp"; + } } } diff --git a/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs b/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs index 5e1593f3c..55e3e9482 100644 --- a/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs +++ b/test/E2ETests/E2ETests/Cosmos/CosmosDBEndToEndTests.cs @@ -32,7 +32,14 @@ public async Task CosmosDBTriggerAndOutput_Succeeds() await CosmosDBHelpers.CreateDocument(expectedDocId, expectedDocId); //Read - var documentId = await CosmosDBHelpers.ReadDocument(expectedDocId); + string documentId = string.Empty; + await TestUtility.RetryAsync(async () => + { + documentId = await CosmosDBHelpers.ReadDocument(expectedDocId); + return documentId is not null; + }); + + //Assert Assert.Equal(expectedDocId, documentId); } finally @@ -98,6 +105,28 @@ public async Task CosmosInput_DocByIdFromRouteData_Succeeds() } } + [Fact] + public async Task CosmosInput_DocByIdFromRouteDataNotFound_Succeeds() + { + string expectedDocId = Guid.NewGuid().ToString(); + string functionPath = $"docsbyroute/{expectedDocId}/{expectedDocId}"; + try + { + //Trigger + HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionPath); + + //Verify + HttpStatusCode expectedStatusCode = HttpStatusCode.NotFound; + + Assert.Equal(expectedStatusCode, response.StatusCode); + } + finally + { + //Clean up + await CosmosDBHelpers.DeleteTestDocuments(expectedDocId); + } + } + [Fact] public async Task CosmosInput_DocByIdFromRouteDataUsingSqlQuery_Succeeds() { diff --git a/test/E2ETests/E2ETests/E2ETests.csproj b/test/E2ETests/E2ETests/E2ETests.csproj index cf3d987f4..a2908ea02 100644 --- a/test/E2ETests/E2ETests/E2ETests.csproj +++ b/test/E2ETests/E2ETests/E2ETests.csproj @@ -1,22 +1,23 @@  - net7.0 + net8.0 Microsoft.Azure.Functions.Worker.E2ETests Microsoft.Azure.Functions.Worker.E2ETests disable - - - - - - - - - + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/E2ETests/E2ETests/Fixtures/FixtureHelpers.cs b/test/E2ETests/E2ETests/Fixtures/FixtureHelpers.cs index 68c126f99..3b31026b4 100644 --- a/test/E2ETests/E2ETests/Fixtures/FixtureHelpers.cs +++ b/test/E2ETests/E2ETests/Fixtures/FixtureHelpers.cs @@ -13,11 +13,11 @@ namespace Microsoft.Azure.Functions.Tests.E2ETests { public static class FixtureHelpers { - public static Process GetFuncHostProcess(bool enableAuth = false) + public static Process GetFuncHostProcess(bool enableAuth = false, string testAppName = null) { var funcProcess = new Process(); var rootDir = Path.GetFullPath(@"../../../../../.."); - var e2eAppBinPath = Path.Combine(rootDir, @"test/E2ETests/E2EApps/E2EApp/bin"); + var e2eAppBinPath = Path.Combine(rootDir, "test", "E2ETests", "E2EApps", testAppName, "bin"); string e2eHostJson = Directory.GetFiles(e2eAppBinPath, "host.json", SearchOption.AllDirectories).FirstOrDefault(); if (e2eHostJson == null) @@ -27,7 +27,7 @@ public static Process GetFuncHostProcess(bool enableAuth = false) var e2eAppPath = Path.GetDirectoryName(e2eHostJson); - var cliPath = Path.Combine(rootDir, @"Azure.Functions.Cli/func"); + var cliPath = Path.Combine(rootDir, "Azure.Functions.Cli", "func"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -45,9 +45,7 @@ public static Process GetFuncHostProcess(bool enableAuth = false) funcProcess.StartInfo.CreateNoWindow = true; funcProcess.StartInfo.WorkingDirectory = e2eAppPath; funcProcess.StartInfo.FileName = cliPath; - funcProcess.StartInfo.ArgumentList.Add("host"); funcProcess.StartInfo.ArgumentList.Add("start"); - funcProcess.StartInfo.ArgumentList.Add("--csharp"); funcProcess.StartInfo.ArgumentList.Add("--verbose"); if (enableAuth) diff --git a/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs b/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs index 6674d0cad..2eedeb61f 100644 --- a/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs +++ b/test/E2ETests/E2ETests/Fixtures/FunctionAppFixture.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.Diagnostics; using System.IO; using System.Net.Http; @@ -19,18 +20,23 @@ public class FunctionAppFixture : IAsyncLifetime private readonly ILogger _logger; private bool _disposed; private Process _funcProcess; - private JobObjectRegistry _jobObjectRegistry; + private string _testApp = Constants.TestAppNames.E2EApp; public FunctionAppFixture(IMessageSink messageSink) { - // initialize logging + // initialize logging ILoggerFactory loggerFactory = new LoggerFactory(); TestLogs = new TestLoggerProvider(messageSink); loggerFactory.AddProvider(TestLogs); _logger = loggerFactory.CreateLogger(); } + internal FunctionAppFixture(IMessageSink messageSink, string testApp) : this(messageSink) + { + _testApp = testApp; + } + public async Task InitializeAsync() { // start host via CLI if testing locally @@ -42,7 +48,7 @@ public async Task InitializeAsync() // start functions process _logger.LogInformation($"Starting functions host for {Constants.FunctionAppCollectionName}..."); - _funcProcess = FixtureHelpers.GetFuncHostProcess(); + _funcProcess = FixtureHelpers.GetFuncHostProcess(testAppName: _testApp); string workingDir = _funcProcess.StartInfo.WorkingDirectory; _logger.LogInformation($" Working dir: '${workingDir}' Exists: '{Directory.Exists(workingDir)}'"); string fileName = _funcProcess.StartInfo.FileName; @@ -51,18 +57,29 @@ public async Task InitializeAsync() if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { // Currently only HTTP is supported in Linux CI. - _funcProcess.StartInfo.ArgumentList.Add("--functions"); - _funcProcess.StartInfo.ArgumentList.Add("HelloFromQuery"); - _funcProcess.StartInfo.ArgumentList.Add("HelloFromJsonBody"); - _funcProcess.StartInfo.ArgumentList.Add("HelloUsingPoco"); - _funcProcess.StartInfo.ArgumentList.Add("HelloWithNoResponse"); - _funcProcess.StartInfo.ArgumentList.Add("PocoFromBody"); - _funcProcess.StartInfo.ArgumentList.Add("PocoBeforeRouteParameters"); - _funcProcess.StartInfo.ArgumentList.Add("PocoAfterRouteParameters"); - _funcProcess.StartInfo.ArgumentList.Add("ExceptionFunction"); - _funcProcess.StartInfo.ArgumentList.Add("PocoWithoutBindingSource"); - _funcProcess.StartInfo.ArgumentList.Add("HelloPascal"); - _funcProcess.StartInfo.ArgumentList.Add("HelloAllCaps"); + switch (_testApp) + { + case Constants.TestAppNames.E2EApp: + _funcProcess.StartInfo.ArgumentList.Add("--functions"); + _funcProcess.StartInfo.ArgumentList.Add("HelloFromQuery"); + _funcProcess.StartInfo.ArgumentList.Add("HelloFromJsonBody"); + _funcProcess.StartInfo.ArgumentList.Add("HelloUsingPoco"); + _funcProcess.StartInfo.ArgumentList.Add("HelloWithNoResponse"); + _funcProcess.StartInfo.ArgumentList.Add("PocoFromBody"); + _funcProcess.StartInfo.ArgumentList.Add("PocoBeforeRouteParameters"); + _funcProcess.StartInfo.ArgumentList.Add("PocoAfterRouteParameters"); + _funcProcess.StartInfo.ArgumentList.Add("ExceptionFunction"); + _funcProcess.StartInfo.ArgumentList.Add("PocoWithoutBindingSource"); + _funcProcess.StartInfo.ArgumentList.Add("HelloPascal"); + _funcProcess.StartInfo.ArgumentList.Add("HelloAllCaps"); + break; + case Constants.TestAppNames.E2EAspNetCoreApp: + _funcProcess.StartInfo.ArgumentList.Add("--functions"); + _funcProcess.StartInfo.ArgumentList.Add("HttpWithCancellationTokenNotUsed"); + _funcProcess.StartInfo.ArgumentList.Add("HttpWithCancellationTokenIgnored"); + _funcProcess.StartInfo.ArgumentList.Add("HttpWithCancellationTokenHandled"); + break; + } } await CosmosDBHelpers.TryCreateDocumentCollectionsAsync(_logger); @@ -114,7 +131,6 @@ await TestUtility.RetryAsync(async () => internal TestLoggerProvider TestLogs { get; private set; } - public Task DisposeAsync() { if (!_disposed) diff --git a/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs b/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs index f146c3396..fc8e70fcb 100644 --- a/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs +++ b/test/E2ETests/E2ETests/Helpers/CosmosDBHelpers.cs @@ -4,77 +4,65 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.Functions.Tests.E2ETests { public static class CosmosDBHelpers { - private static readonly DocumentClient _docDbClient; - private static readonly Uri inputCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName); - private static readonly Uri outputCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName); - private static readonly Uri leasesCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.LeaseCollectionName); + private static readonly CosmosClient _cosmosClient; + private static readonly Container _inputContainer; + private static readonly Container _outputContainer; + private static readonly Container _leaseContainer; static CosmosDBHelpers() { - var builder = new System.Data.Common.DbConnectionStringBuilder - { - ConnectionString = Constants.CosmosDB.CosmosDBConnectionStringSetting - }; - var serviceUri = new Uri(builder["AccountEndpoint"].ToString()); - _docDbClient = new DocumentClient(serviceUri, builder["AccountKey"].ToString()); + _cosmosClient = new CosmosClient(Constants.CosmosDB.CosmosDBConnectionStringSetting); + + var database = _cosmosClient.GetDatabase(Constants.CosmosDB.DbName); + _inputContainer = database.GetContainer(Constants.CosmosDB.InputCollectionName); + _outputContainer = database.GetContainer(Constants.CosmosDB.OutputCollectionName); + _leaseContainer = database.GetContainer(Constants.CosmosDB.LeaseCollectionName); } // keep public async static Task CreateDocument(string docId, string docText = "test") { - Document documentToTest = new Document() { Id = docId }; - documentToTest.SetPropertyValue("Text", docText); - - _ = await _docDbClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName), documentToTest); + var documentToTest = new { id = docId, Text = docText }; + await _inputContainer.CreateItemAsync(documentToTest, new PartitionKey(docId)); } // keep public async static Task ReadDocument(string docId) { - var docUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId); - Document retrievedDocument = null; - await TestUtility.RetryAsync(async () => + try + { + var response = await _outputContainer.ReadItemAsync(docId, new PartitionKey(docId)); + return response.Resource?.id; + } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - try - { - retrievedDocument = await _docDbClient.ReadDocumentAsync(docUri, new RequestOptions { PartitionKey = new PartitionKey(docId) }); - return true; - } - catch (DocumentClientException ex) when (ex.Error.Code == "NotFound") - { - return false; - } - }, pollingInterval: 500); - - return retrievedDocument?.Id; + return null; + } } // keep public async static Task DeleteTestDocuments(string docId) { - var inputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName, docId); - await DeleteDocument(inputDocUri, docId); - var outputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId); - await DeleteDocument(outputDocUri, docId); + await DeleteDocument(_inputContainer, docId); + await DeleteDocument(_outputContainer, docId); } - private async static Task DeleteDocument(Uri docUri, string docId) + private async static Task DeleteDocument(Container container, string docId) { try { - await _docDbClient.DeleteDocumentAsync(docUri, new RequestOptions { PartitionKey = new PartitionKey(docId) }); + await container.DeleteItemAsync(docId, new PartitionKey(docId)); } - catch (Exception) + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - //ignore + // Ignore if the document is already deleted } } @@ -82,75 +70,66 @@ private async static Task CanConnectAsync(ILogger logger) { try { - await new HttpClient().GetAsync(_docDbClient.ServiceEndpoint); + var response = await _cosmosClient.ReadAccountAsync(); + return response != null; } catch { - // typically "the target machine actively refused it" if the emulator isn't running. - logger.LogError($"Could not connect to CosmosDB endpoint: '{_docDbClient.ServiceEndpoint}'. Are you using the emulator?"); + logger.LogError($"Could not connect to CosmosDB. Check the emulator or connection string."); return false; } - - return true; } - // keep public async static Task TryCreateDocumentCollectionsAsync(ILogger logger) { if (!await CanConnectAsync(logger)) { - // This can hang if the service is unavailable. Just return and let tests fail. - // The Cosmos tests may be filtered out anyway without an emulator running. return false; } - Database db = await _docDbClient.CreateDatabaseIfNotExistsAsync(new Database { Id = Constants.CosmosDB.DbName }); - Uri dbUri = UriFactory.CreateDatabaseUri(db.Id); + var databaseResponse = await _cosmosClient.CreateDatabaseIfNotExistsAsync(Constants.CosmosDB.DbName); + var database = databaseResponse.Database; await Task.WhenAll( - CreateCollection(dbUri, Constants.CosmosDB.InputCollectionName), - CreateCollection(dbUri, Constants.CosmosDB.OutputCollectionName), - CreateCollection(dbUri, Constants.CosmosDB.LeaseCollectionName)); + CreateCollection(database, Constants.CosmosDB.InputCollectionName), + CreateCollection(database, Constants.CosmosDB.OutputCollectionName), + CreateCollection(database, Constants.CosmosDB.LeaseCollectionName)); return true; } public async static Task DeleteDocumentCollections() { + var database = _cosmosClient.GetDatabase(Constants.CosmosDB.DbName); await Task.WhenAll( - DeleteCollection(inputCollectionsUri), - DeleteCollection(outputCollectionsUri), - DeleteCollection(leasesCollectionsUri)); + DeleteCollection(database, Constants.CosmosDB.InputCollectionName), + DeleteCollection(database, Constants.CosmosDB.OutputCollectionName), + DeleteCollection(database, Constants.CosmosDB.LeaseCollectionName)); } - private async static Task DeleteCollection(Uri collectionUri) + private async static Task DeleteCollection(Database database, string collectionName) { try { - await _docDbClient.DeleteDocumentCollectionAsync(collectionUri); + var container = database.GetContainer(collectionName); + await container.DeleteContainerAsync(); } - catch (Exception) + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - //Ignore + // Ignore if the container is already deleted } } - private async static Task CreateCollection(Uri dbUri, string collectioName) + private async static Task CreateCollection(Database database, string collectionName) { - var pkd = new PartitionKeyDefinition(); - pkd.Paths.Add("/id"); - DocumentCollection collection = new DocumentCollection() + var containerProperties = new ContainerProperties { - Id = collectioName, - PartitionKey = pkd + Id = collectionName, + PartitionKeyPath = "/id" }; - await _docDbClient.CreateDocumentCollectionIfNotExistsAsync(dbUri, collection, - new RequestOptions() - { - PartitionKey = new PartitionKey("/id"), - OfferThroughput = 400 - }); + + await database.CreateContainerIfNotExistsAsync(containerProperties, throughput: 400); } } } diff --git a/test/E2ETests/E2ETests/Helpers/FactAttributes/IgnoreOnNetFxTestRunTheory.cs b/test/E2ETests/E2ETests/Helpers/FactAttributes/IgnoreOnNetFxTestRunTheory.cs new file mode 100644 index 000000000..b9e1adf48 --- /dev/null +++ b/test/E2ETests/E2ETests/Helpers/FactAttributes/IgnoreOnNetFxTestRunTheory.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Xunit; + +namespace Microsoft.Azure.Functions.Tests.E2ETests +{ + public sealed class IgnoreOnNetFxTestRunTheory : TheoryAttribute + { + public IgnoreOnNetFxTestRunTheory() + { + if (IsNetFxTestRun()) + { + Skip = "Ignore when test run is using netFx as AspNetCore is not supported."; + } + } + + private static bool IsNetFxTestRun() + => string.Equals(Environment.GetEnvironmentVariable("DOTNET_VERSION"), "netfx", StringComparison.OrdinalIgnoreCase); + } +} \ No newline at end of file diff --git a/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs b/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs index d5f788498..e44fbce98 100644 --- a/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs +++ b/test/E2ETests/E2ETests/Helpers/HttpHelpers.cs @@ -5,18 +5,19 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Azure.Functions.Tests.E2ETests { class HttpHelpers { - public static async Task InvokeHttpTrigger(string functionName, string queryString = "") + public static async Task InvokeHttpTrigger(string functionName, string queryString = "", CancellationToken cancellationToken = default) { // Basic http request HttpRequestMessage request = GetTestRequest(functionName, queryString); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain")); - return await GetResponseMessage(request); + return await GetResponseMessage(request, cancellationToken); } public static async Task InvokeHttpTriggerWithBody(string functionName, string body, string mediaType) @@ -64,12 +65,12 @@ private static HttpRequestMessage GetTestRequest(string functionName, string que }; } - private static async Task GetResponseMessage(HttpRequestMessage request) + private static async Task GetResponseMessage(HttpRequestMessage request, CancellationToken cancellationToken = default) { HttpResponseMessage response = null; using (var httpClient = new HttpClient()) { - response = await httpClient.SendAsync(request); + response = await httpClient.SendAsync(request, cancellationToken); } return response; diff --git a/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs b/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs deleted file mode 100644 index 6576e99d7..000000000 --- a/test/FunctionMetadataGeneratorTests/ExtensionsCsProjGeneratorTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.Azure.Functions.Worker.Sdk; -using Xunit; - -namespace Microsoft.Azure.Functions.SdkTests -{ - public class ExtensionsCsProjGeneratorTests - { - public enum FuncVersion - { - V3, - V4, - } - - [Theory] - [InlineData(FuncVersion.V3)] - [InlineData(FuncVersion.V4)] - public void GetCsProjContent_Succeeds(FuncVersion version) - { - var generator = GetGenerator(version); - string actual = generator.GetCsProjContent().Replace("\r\n", "\n"); - string expected = ExpectedCsproj(version).Replace("\r\n", "\n"); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(FuncVersion.V3)] - [InlineData(FuncVersion.V4)] - public void GetCsProjContent_IncrementalSupport(FuncVersion version) - { - DateTime RunGenerate(string subPath, out string contents) - { - var generator = GetGenerator(version, subPath); - generator.Generate(); - - string path = Path.Combine(subPath, ExtensionsCsprojGenerator.ExtensionsProjectName); - contents = File.ReadAllText(path); - var csproj = new FileInfo(Path.Combine(subPath, ExtensionsCsprojGenerator.ExtensionsProjectName)); - return csproj.LastWriteTimeUtc; - } - - string subPath = Guid.NewGuid().ToString(); - DateTime firstRun = RunGenerate(subPath, out string first); - DateTime secondRun = RunGenerate(subPath, out string second); - - Assert.NotEqual(firstRun, secondRun); - Assert.Equal(first, second); - } - - static ExtensionsCsprojGenerator GetGenerator(FuncVersion version, string subPath = "") - { - IDictionary extensions = new Dictionary - { - { "Microsoft.Azure.WebJobs.Extensions.Storage", "4.0.3" }, - { "Microsoft.Azure.WebJobs.Extensions.Http", "3.0.0" }, - { "Microsoft.Azure.WebJobs.Extensions", "2.0.0" }, - }; - - return version switch - { - FuncVersion.V3 => new ExtensionsCsprojGenerator(extensions, subPath, "v3", Constants.NetCoreApp, Constants.NetCoreVersion31), - FuncVersion.V4 => new ExtensionsCsprojGenerator(extensions, subPath, "v4", Constants.NetCoreApp, Constants.NetCoreVersion6), - _ => throw new ArgumentOutOfRangeException(nameof(version)), - }; - } - - private static string ExpectedCsproj(FuncVersion version) - => version switch - { - FuncVersion.V3 => ExpectedCsProjV3(), - FuncVersion.V4 => ExpectedCsProjV4(), - _ => throw new ArgumentOutOfRangeException(nameof(version)), - }; - - private static string ExpectedCsProjV3() - { - return @" - - - netcoreapp3.1 - Release - Microsoft.Azure.Functions.Worker.Extensions - true - false - - - - - - - - - - - - - - - -"; - } - - private static string ExpectedCsProjV4() - { - return @" - - - net6.0 - Release - Microsoft.Azure.Functions.Worker.Extensions - true - false - - - - - - - - - - - - - - - -"; - } - } -} diff --git a/test/FunctionMetadataGeneratorTests/SdkTests.csproj b/test/FunctionMetadataGeneratorTests/SdkTests.csproj deleted file mode 100644 index da0bc2af8..000000000 --- a/test/FunctionMetadataGeneratorTests/SdkTests.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - net7.0 - Microsoft.Azure.Functions.SdkTests - Microsoft.Azure.Functions.SdkTests - true - ..\..\key.snk - - $(NoWarn);NU1608;NU1701 - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - diff --git a/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj b/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj index 48b614c1b..06a9e64f8 100644 --- a/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj +++ b/test/Resources/Projects/FunctionApp01/FunctionApp01.csproj @@ -12,15 +12,12 @@ - - - - + diff --git a/test/Resources/Projects/FunctionExt01/FunctionExt01.csproj b/test/Resources/Projects/FunctionExt01/FunctionExt01.csproj new file mode 100644 index 000000000..904b22ce1 --- /dev/null +++ b/test/Resources/Projects/FunctionExt01/FunctionExt01.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + + + + + + + + + <_Parameter1>Microsoft.Azure.WebJobs.Extensions.Storage + <_Parameter2>5.3.1 + <_Parameter3>true + <_Parameter3_IsLiteral>true + + + + diff --git a/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj b/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj index d0a849c7a..872d41b88 100644 --- a/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj +++ b/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj @@ -1,22 +1,25 @@  - net7.0 + net8.0 false $(NoWarn);NU1701;RS1014 - + + + - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -29,10 +32,8 @@ - - - + diff --git a/test/SdkE2ETests/Contents/functions.metadata b/test/Sdk.E2ETests/Contents/functions.metadata similarity index 100% rename from test/SdkE2ETests/Contents/functions.metadata rename to test/Sdk.E2ETests/Contents/functions.metadata diff --git a/test/SdkE2ETests/InnerBuildTests.cs b/test/Sdk.E2ETests/InnerBuildTests.cs similarity index 83% rename from test/SdkE2ETests/InnerBuildTests.cs rename to test/Sdk.E2ETests/InnerBuildTests.cs index 01df7d661..e38822c14 100644 --- a/test/SdkE2ETests/InnerBuildTests.cs +++ b/test/Sdk.E2ETests/InnerBuildTests.cs @@ -7,7 +7,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Azure.Functions.SdkE2ETests +namespace Microsoft.Azure.Functions.Sdk.E2ETests { public class InnerBuildTests { @@ -34,20 +34,20 @@ public async Task Build_ScansReferences() JToken expectedExtensionsJson = JObject.Parse(@"{ ""extensions"": [ { - ""name"": ""SqlDurabilityProvider"", - ""typeName"": ""DurableTask.SqlServer.AzureFunctions.SqlDurabilityProviderStartup, DurableTask.SqlServer.AzureFunctions, Version=1.4.0.0, Culture=neutral, PublicKeyToken=2ea3c3a96309d850"", - ""hintPath"": ""./.azurefunctions/DurableTask.SqlServer.AzureFunctions.dll"" + ""name"": ""AzureStorageBlobs"", + ""typeName"": ""Microsoft.Azure.WebJobs.Extensions.Storage.AzureStorageBlobsWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.Storage.Blobs, Version=5.3.1.0, Culture=neutral, PublicKeyToken=92742159e12e44c8"", + ""hintPath"": ""./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.dll"" }, { - ""name"": ""DurableTask"", - ""typeName"": ""Microsoft.Azure.WebJobs.Extensions.DurableTask.DurableTaskWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.DurableTask, Version=2.0.0.0, Culture=neutral, PublicKeyToken=014045d636e89289"", - ""hintPath"": ""./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.DurableTask.dll"" + ""name"": ""AzureStorageQueues"", + ""typeName"": ""Microsoft.Azure.WebJobs.Extensions.Storage.AzureStorageQueuesWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.Storage.Queues, Version=5.1.3.0, Culture=neutral, PublicKeyToken=92742159e12e44c8"", + ""hintPath"": ""./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.Storage.Queues.dll"" }, { ""name"": ""Startup"", ""typeName"": ""Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.Startup, Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c"", ""hintPath"": ""./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll"" - } + }, ] }"); diff --git a/test/SdkE2ETests/ProcessWrapper.cs b/test/Sdk.E2ETests/ProcessWrapper.cs similarity index 64% rename from test/SdkE2ETests/ProcessWrapper.cs rename to test/Sdk.E2ETests/ProcessWrapper.cs index 53c4fb7dc..ae250e4ae 100644 --- a/test/SdkE2ETests/ProcessWrapper.cs +++ b/test/Sdk.E2ETests/ProcessWrapper.cs @@ -3,16 +3,31 @@ using System; using System.Diagnostics; +using System.Text; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; -namespace Microsoft.Azure.Functions.SdkE2ETests +namespace Microsoft.Azure.Functions.Sdk.E2ETests { public class ProcessWrapper { + public async Task RunProcess(string fileName, string arguments, string workingDirectory, ITestOutputHelper testOutputHelper = null) { + return await RunProcessInternal(fileName, arguments, workingDirectory, testOutputHelper); + } + + public async Task> RunProcessForOutput(string fileName, string arguments, string workingDirectory, ITestOutputHelper testOutputHelper = null) + { + StringBuilder processOutputStringBuilder = new StringBuilder(); + var exitCode = await RunProcessInternal(fileName, arguments, workingDirectory, testOutputHelper, processOutputStringBuilder); + return new Tuple(exitCode, processOutputStringBuilder.ToString()); + } + + private async Task RunProcessInternal(string fileName, string arguments, string workingDirectory, ITestOutputHelper testOutputHelper = null, StringBuilder processOutputBuilder = null) + { + SemaphoreSlim processExitSemaphore = new SemaphoreSlim(0, 1); ProcessStartInfo startInfo = new ProcessStartInfo @@ -39,6 +54,10 @@ public class ProcessWrapper if (o.Data != null) { testOutputHelper.WriteLine($"[{DateTime.UtcNow:O}] Error: {o.Data}"); + if (processOutputBuilder != null) + { + processOutputBuilder.AppendLine(o.Data); + } } }; @@ -47,6 +66,10 @@ public class ProcessWrapper if (o.Data != null) { testOutputHelper.WriteLine($"[{DateTime.UtcNow:O}] {o.Data}"); + if (processOutputBuilder != null) + { + processOutputBuilder.AppendLine(o.Data); + } } }; @@ -65,4 +88,5 @@ public class ProcessWrapper return testProcess?.ExitCode; } } + } diff --git a/test/SdkE2ETests/PublishTests.cs b/test/Sdk.E2ETests/PublishTests.cs similarity index 69% rename from test/SdkE2ETests/PublishTests.cs rename to test/Sdk.E2ETests/PublishTests.cs index cee2b00a9..d707922e2 100644 --- a/test/SdkE2ETests/PublishTests.cs +++ b/test/Sdk.E2ETests/PublishTests.cs @@ -9,7 +9,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Azure.Functions.SdkE2ETests +namespace Microsoft.Azure.Functions.Sdk.E2ETests { public class PublishTests { @@ -34,6 +34,34 @@ public async Task Publish_Rid() await RunPublishTest(outputDir, "-r win-x86"); } + [Fact] + // This test requires the Docker daemon to be installed and running + // It is excluded through the Sdk.E2ETests_default.runsettings file from normal tests + // To run the test, use `dotnet test -s Sdk.E2ETests_dockertests.runsettings` + [Trait("Requirement", "Docker")] + public async Task Publish_Container() + { + string outputDir = await TestUtility.InitializeTestAsync(_testOutputHelper, nameof(Publish_Container)); + var repository = nameof(Sdk.E2ETests).ToLower(); + var imageTag = nameof(Publish_Container); + + // setup test environment state in case there is leftover data from previous runs + await TestUtility.RemoveDockerTestImage(repository, imageTag, _testOutputHelper); + + // perform the publish + await RunPublishTest(outputDir, $"--no-restore /t:PublishContainer --property:ContainerRepository={repository} --property:ContainerImageTag={imageTag}"); + + // validate the image base + Tuple inspectResults = await new ProcessWrapper().RunProcessForOutput("docker", $"inspect {repository}:{imageTag} --format \"{{{{ index .Config.Labels \\\"org.opencontainers.image.base.name\\\"}}}}\"", outputDir, _testOutputHelper); + var inspectExitCode = inspectResults.Item1; + var inspectOutput = inspectResults.Item2; + Assert.True(inspectExitCode.HasValue && inspectExitCode.Value == 0); + Assert.Matches("mcr\\.microsoft\\.com/azure-functions/dotnet-isolated:(\\d)+-dotnet-isolated(\\d+\\.\\d+)", inspectOutput); + + // clean up + await TestUtility.RemoveDockerTestImage(repository, imageTag, _testOutputHelper); + } + private async Task RunPublishTest(string outputDir, string additionalParams = null) { // Name of the csproj @@ -60,21 +88,21 @@ private async Task RunPublishTest(string outputDir, string additionalParams = nu { extensions = new[] { - new Extension("Startup", - "Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.Startup, Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c", - @"./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll"), new Extension("AzureStorageBlobs", "Microsoft.Azure.WebJobs.Extensions.Storage.AzureStorageBlobsWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.Storage.Blobs, Version=5.3.1.0, Culture=neutral, PublicKeyToken=92742159e12e44c8", @"./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.dll"), new Extension("AzureStorageQueues", "Microsoft.Azure.WebJobs.Extensions.Storage.AzureStorageQueuesWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.Storage.Queues, Version=5.3.1.0, Culture=neutral, PublicKeyToken=92742159e12e44c8", - @"./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.Storage.Queues.dll") + @"./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.Storage.Queues.dll"), + new Extension("Startup", + "Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.Startup, Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c", + @"./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll"), } }); Assert.True(JToken.DeepEquals(extensionsJsonContents, expected), $"Actual: {extensionsJsonContents}{Environment.NewLine}Expected: {expected}"); // Verify functions.metadata - TestUtility.ValidateFunctionsMetadata(functionsMetadataPath, "Microsoft.Azure.Functions.SdkE2ETests.Contents.functions.metadata"); + TestUtility.ValidateFunctionsMetadata(functionsMetadataPath, "Microsoft.Azure.Functions.Sdk.E2ETests.Contents.functions.metadata"); } private class Extension diff --git a/test/SdkE2ETests/SdkE2ETests.csproj b/test/Sdk.E2ETests/Sdk.E2ETests.csproj similarity index 60% rename from test/SdkE2ETests/SdkE2ETests.csproj rename to test/Sdk.E2ETests/Sdk.E2ETests.csproj index d048f470d..5a3bdf227 100644 --- a/test/SdkE2ETests/SdkE2ETests.csproj +++ b/test/Sdk.E2ETests/Sdk.E2ETests.csproj @@ -1,13 +1,17 @@  - net7.0 - Microsoft.Azure.Functions.SdkE2ETests - Microsoft.Azure.Functions.SdkE2ETests + net8.0 + Microsoft.Azure.Functions.Sdk.E2ETests + Microsoft.Azure.Functions.Sdk.E2ETests true ..\..\key.snk + + $(MSBuildProjectDirectory)\Sdk.E2ETests_default.runsettings + + @@ -17,11 +21,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,9 +36,7 @@ - - Always - + diff --git a/test/Sdk.E2ETests/Sdk.E2ETests_default.runsettings b/test/Sdk.E2ETests/Sdk.E2ETests_default.runsettings new file mode 100644 index 000000000..723105e33 --- /dev/null +++ b/test/Sdk.E2ETests/Sdk.E2ETests_default.runsettings @@ -0,0 +1,6 @@ + + + + (Requirement != Docker) + + \ No newline at end of file diff --git a/test/Sdk.E2ETests/Sdk.E2ETests_dockertests.runsettings b/test/Sdk.E2ETests/Sdk.E2ETests_dockertests.runsettings new file mode 100644 index 000000000..7d33ac51e --- /dev/null +++ b/test/Sdk.E2ETests/Sdk.E2ETests_dockertests.runsettings @@ -0,0 +1,6 @@ + + + + (Requirement = Docker) + + \ No newline at end of file diff --git a/test/SdkE2ETests/TestUtility.cs b/test/Sdk.E2ETests/TestUtility.cs similarity index 90% rename from test/SdkE2ETests/TestUtility.cs rename to test/Sdk.E2ETests/TestUtility.cs index b636691fd..b1217ad3f 100644 --- a/test/SdkE2ETests/TestUtility.cs +++ b/test/Sdk.E2ETests/TestUtility.cs @@ -11,7 +11,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Azure.Functions.SdkE2ETests +namespace Microsoft.Azure.Functions.Sdk.E2ETests { public static class TestUtility { @@ -27,14 +27,14 @@ public static class TestUtility // Paths and executables public static readonly string DotNetExecutable = "dotnet"; - public static readonly string PathToRepoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, @"..\..\..\..\..\")); + public static readonly string PathToRepoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../")); public static readonly string SrcRoot = Path.Combine(PathToRepoRoot, "src"); public static readonly string SdkSolutionRoot = Path.Combine(PathToRepoRoot, "sdk"); public static readonly string SdkProjectRoot = Path.Combine(SdkSolutionRoot, "Sdk"); public static readonly string TestRoot = Path.Combine(PathToRepoRoot, "test"); public static readonly string SamplesRoot = Path.Combine(PathToRepoRoot, "samples"); public static readonly string LocalPackages = Path.Combine(PathToRepoRoot, "local"); - public static readonly string TestOutputDir = Path.Combine(Path.GetTempPath(), "FunctionsWorkerSdkE2ETests"); + public static readonly string TestOutputDir = Path.Combine(Path.GetTempPath(), "FunctionsWorkerSdk.E2ETests"); public static readonly string TestResourcesProjectsRoot = Path.Combine(TestRoot, "Resources", "Projects"); public static readonly string NuGetOrgPackages = "https://api.nuget.org/v3/index.json"; @@ -135,5 +135,12 @@ private static string InitializeOutputDir(string testName) return outputDir; } + + public static async Task RemoveDockerTestImage(string repository, string imageTag, ITestOutputHelper outputHelper) + { + outputHelper.WriteLine($"Removing image {repository}:{imageTag} from local registry"); + int? rmiExitCode = await new ProcessWrapper().RunProcess("docker", $"rmi -f {repository}:{imageTag}", TestOutputDir, outputHelper); + Assert.True(rmiExitCode.HasValue && rmiExitCode.Value == 0); // daemon may still error if the image doesn't exist, but it will still return 0 + } } } diff --git a/test/SdkE2ETests/ZipDeployTests.cs b/test/Sdk.E2ETests/ZipDeployTests.cs similarity index 50% rename from test/SdkE2ETests/ZipDeployTests.cs rename to test/Sdk.E2ETests/ZipDeployTests.cs index d0a40c8ff..5d8c522a2 100644 --- a/test/SdkE2ETests/ZipDeployTests.cs +++ b/test/Sdk.E2ETests/ZipDeployTests.cs @@ -1,12 +1,13 @@ -using System.IO; +using System; +using System.IO; +using System.Runtime.InteropServices; using System.Threading.Tasks; using ICSharpCode.SharpZipLib.Zip; -using Microsoft.Azure.Functions.SdkE2ETests; using Microsoft.NET.Sdk.Functions.MSBuild.Tasks; using Xunit; using Xunit.Abstractions; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.E2ETests { public class ZipDeployTests { @@ -35,31 +36,33 @@ public async Task CreateZipFileFromDirectory_SetsExecutableFlag_WhenSelfContaine string projectFileDirectory = Path.Combine(TestUtility.SamplesRoot, "FunctionApp", "FunctionApp.csproj"); - await TestUtility.RestoreAndPublishProjectAsync(projectFileDirectory, directoryToZip, $"-r {rid} --self-contained {selfContained}", _testOutputHelper); + await TestUtility.RestoreAndPublishProjectAsync( + projectFileDirectory, directoryToZip, $"-r {rid} --self-contained {selfContained}", _testOutputHelper); CreateZipFileTask.CreateZipFileFromDirectory(directoryToZip, zipName); - using (var zip = new ZipFile(zipName)) + using var zip = new ZipFile(zipName); + Assert.Equal(Directory.GetFiles(directoryToZip, "*", SearchOption.AllDirectories).Length, zip.Count); + foreach (ZipEntry entry in zip) { - Assert.Equal(Directory.GetFiles(directoryToZip, "*", SearchOption.AllDirectories).Length, zip.Count); - - for (int i = 0; i < zip.Count; i++) + if (selfContained && (entry.Name == "FunctionApp" || entry.Name == "FunctionApp.exe")) + { + Assert.Equal(3, entry.HostSystem); + Assert.Equal(CreateZipFileTask.UnixExecutablePermissions, entry.ExternalFileAttributes); + } + else if (OperatingSystem.IsWindows()) { - var entry = zip[i]; - if (selfContained && - (entry.Name == "FunctionApp" || entry.Name == "FunctionApp.exe")) - { - Assert.Equal(3, entry.HostSystem); - Assert.Equal(CreateZipFileTask.UnixExecutablePermissions, entry.ExternalFileAttributes); - } - else - { - Assert.Equal(0, entry.HostSystem); - Assert.Equal(0, entry.ExternalFileAttributes); - } + // All other files are default on windows. + Assert.Equal(0, entry.HostSystem); + Assert.Equal(0, entry.ExternalFileAttributes); } + else + { + Assert.Equal(3, entry.HostSystem); - zip.Close(); + // Unix permissions will vary based on the file. Just making sure they have _some_ permissions + Assert.NotEqual(0, entry.ExternalFileAttributes); + } } } } diff --git a/test/SdkE2ETests/xunit.runner.json b/test/Sdk.E2ETests/xunit.runner.json similarity index 100% rename from test/SdkE2ETests/xunit.runner.json rename to test/Sdk.E2ETests/xunit.runner.json diff --git a/test/Sdk.Generator.Tests/ExtensionStartup/ExtensionStartupRunnerGeneratorTests.cs b/test/Sdk.Generator.Tests/ExtensionStartupRunner/ExtensionStartupRunnerGeneratorTests.cs similarity index 96% rename from test/Sdk.Generator.Tests/ExtensionStartup/ExtensionStartupRunnerGeneratorTests.cs rename to test/Sdk.Generator.Tests/ExtensionStartupRunner/ExtensionStartupRunnerGeneratorTests.cs index 1cdab3995..790695209 100644 --- a/test/Sdk.Generator.Tests/ExtensionStartup/ExtensionStartupRunnerGeneratorTests.cs +++ b/test/Sdk.Generator.Tests/ExtensionStartupRunner/ExtensionStartupRunnerGeneratorTests.cs @@ -4,13 +4,15 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Tests.WorkerExtensionsSample; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using Worker.Extensions.Sample_IncorrectImplementation; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests + +namespace Microsoft.Azure.Functions.Sdk.Generator.ExtensionStartupRunner.Tests { public partial class ExtensionStartupRunnerGeneratorTests { @@ -36,7 +38,7 @@ public async Task StartupExecutorCodeGetsGenerated(LanguageVersion languageVersi }; string expectedGeneratedFileName = $"WorkerExtensionStartupCodeExecutor.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using Microsoft.Azure.Functions.Worker; @@ -47,7 +49,7 @@ public async Task StartupExecutorCodeGetsGenerated(LanguageVersion languageVersi namespace TestProject { [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} internal class WorkerExtensionStartupCodeExecutor : global::Microsoft.Azure.Functions.Worker.Core.WorkerExtensionStartup { /// @@ -120,7 +122,7 @@ public async Task DiagnosticErrorsAreReportedWhenStartupTypeIsInvalid(LanguageVe // Our generator will create code for the good implementation // and report 2 diagnostic errors for the bad implementation. string expectedGeneratedFileName = $"WorkerExtensionStartupCodeExecutor.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using Microsoft.Azure.Functions.Worker; @@ -131,7 +133,7 @@ public async Task DiagnosticErrorsAreReportedWhenStartupTypeIsInvalid(LanguageVe namespace MyCompany.MyProject.MyApp { [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} internal class WorkerExtensionStartupCodeExecutor : global::Microsoft.Azure.Functions.Worker.Core.WorkerExtensionStartup { /// diff --git a/test/Sdk.Generator.Tests/ExtensionStartup/NotGeneratedTests.cs b/test/Sdk.Generator.Tests/ExtensionStartupRunner/NotGeneratedTests.cs similarity index 93% rename from test/Sdk.Generator.Tests/ExtensionStartup/NotGeneratedTests.cs rename to test/Sdk.Generator.Tests/ExtensionStartupRunner/NotGeneratedTests.cs index 5f1191a01..5e0935d12 100644 --- a/test/Sdk.Generator.Tests/ExtensionStartup/NotGeneratedTests.cs +++ b/test/Sdk.Generator.Tests/ExtensionStartupRunner/NotGeneratedTests.cs @@ -2,11 +2,13 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Tests.WorkerExtensionsSample; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests + +namespace Microsoft.Azure.Functions.Sdk.Generator.ExtensionStartupRunner.Tests { public partial class ExtensionStartupRunnerGeneratorTests { diff --git a/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs index 6a446b739..ce829732e 100644 --- a/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs +++ b/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs @@ -1,14 +1,15 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionExecutor.Tests { public partial class FunctionExecutorGeneratorTests { @@ -70,7 +71,7 @@ public HttpResponseData Foo([HttpTrigger(AuthorizationLevel.User, "get")] HttpRe namespace TestProject { [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor { private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -138,7 +139,7 @@ public DirectFunctionExecutor(global::Microsoft.Azure.Functions.Worker.IFunction private global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor CreateDefaultExecutorInstance(global::Microsoft.Azure.Functions.Worker.FunctionContext context) { - var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, Microsoft.Azure.Functions.Worker.Core, Version=1.19.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c"; + var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, Microsoft.Azure.Functions.Worker.Core, Version=2.51.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c"; var defaultExecutorType = global::System.Type.GetType(defaultExecutorFullName); return ActivatorUtilities.CreateInstance(context.InstanceServices, defaultExecutorType) as global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor; diff --git a/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs b/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs index a94d51fa4..dae5ca8db 100644 --- a/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs +++ b/test/Sdk.Generator.Tests/FunctionExecutor/FunctionExecutorGeneratorTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Azure.Messaging.EventHubs; using Azure.Storage.Queues.Models; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; @@ -15,7 +16,7 @@ using Microsoft.Extensions.Logging; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionExecutor.Tests { public partial class FunctionExecutorGeneratorTests { @@ -116,7 +117,7 @@ public void Run2([QueueTrigger(""myqueue-items"")] string message) namespace TestProject {{ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {Constants.GeneratedCodeAttribute} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor {{ private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -238,7 +239,7 @@ public HttpResponseData Run2([HttpTrigger(AuthorizationLevel.User, ""get"")] Htt namespace MyCompany.MyProject.MyApp {{ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {Constants.GeneratedCodeAttribute} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor {{ private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -387,7 +388,7 @@ public static Task RunAsync1([EventHubTrigger(""items"", Connection = ""Con"")] namespace TestProject {{ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {Constants.GeneratedCodeAttribute} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor {{ private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -519,7 +520,7 @@ public HttpResponseData Run1([HttpTrigger(AuthorizationLevel.User, ""get"")] Htt namespace TestProject {{ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {Constants.GeneratedCodeAttribute} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor {{ private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -613,7 +614,7 @@ public static HttpResponseData FooStatic([HttpTrigger(AuthorizationLevel.User, " namespace TestProject {{ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {Constants.GeneratedCodeAttribute} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor {{ private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -706,7 +707,7 @@ public static HttpResponseData HELLO([HttpTrigger(AuthorizationLevel.User, ""get namespace TestProject {{ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {Constants.GeneratedCodeAttribute} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor {{ private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; @@ -756,11 +757,12 @@ private static string GetExpectedExtensionMethodCode(bool includeAutoStartupType { if (includeAutoStartupType) { - return """ + return $$""" /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class FunctionExecutorHostBuilderExtensions { /// @@ -778,6 +780,7 @@ public static IHostBuilder ConfigureGeneratedFunctionExecutor(this IHostBuilder /// Auto startup class to register the custom implementation generated for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {{Constants.GeneratedCodeAttribute}} public class FunctionExecutorAutoStartup : global::Microsoft.Azure.Functions.Worker.IAutoConfigureStartup { /// @@ -792,11 +795,12 @@ public void Configure(IHostBuilder hostBuilder) """; } - return """ + return $$""" /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class FunctionExecutorHostBuilderExtensions { /// @@ -812,5 +816,104 @@ public static IHostBuilder ConfigureGeneratedFunctionExecutor(this IHostBuilder } """; } + + [Theory] + [InlineData(LanguageVersion.CSharp7_3)] + [InlineData(LanguageVersion.CSharp8)] + [InlineData(LanguageVersion.CSharp9)] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + [InlineData(LanguageVersion.Latest)] + public async Task ObsoleteFunction(LanguageVersion languageVersion) + { + const string inputSourceCode = @" +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Azure.Storage.Queues.Models; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; +namespace TestProject +{ + public class TestProject + { + [Function(""FunctionA"")] + [Obsolete(""Do not use"")] + public HttpResponseData Foo([HttpTrigger(AuthorizationLevel.User, ""get"")] HttpRequestData r, FunctionContext c) + { + return r.CreateResponse(System.Net.HttpStatusCode.OK); + } + + [Function(""FunctionB"")] + [Obsolete(""Do not use"")] + public static HttpResponseData FooStatic([HttpTrigger(AuthorizationLevel.User, ""get"")] HttpRequestData r, FunctionContext c) + { + return r.CreateResponse(System.Net.HttpStatusCode.OK); + } + } +} +"; + var expectedOutput = $@"// +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Context.Features; +using Microsoft.Azure.Functions.Worker.Invocation; +namespace TestProject +{{ + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {Constants.GeneratedCodeAttribute} + internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor + {{ + private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator; + private readonly Dictionary types = new Dictionary() + {{ + {{ ""TestProject.TestProject"", Type.GetType(""TestProject.TestProject, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"") }} + }}; + + public DirectFunctionExecutor(global::Microsoft.Azure.Functions.Worker.IFunctionActivator functionActivator) + {{ + _functionActivator = functionActivator ?? throw new global::System.ArgumentNullException(nameof(functionActivator)); + }} + + /// + public async global::System.Threading.Tasks.ValueTask ExecuteAsync(global::Microsoft.Azure.Functions.Worker.FunctionContext context) + {{ + var inputBindingFeature = context.Features.Get(); + var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context); + var inputArguments = inputBindingResult.Values; + + if (string.Equals(context.FunctionDefinition.EntryPoint, ""TestProject.TestProject.Foo"", StringComparison.Ordinal)) + {{ + var instanceType = types[""TestProject.TestProject""]; + var i = _functionActivator.CreateInstance(instanceType, context) as global::TestProject.TestProject; +#pragma warning disable CS0618 + context.GetInvocationResult().Value = i.Foo((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]); +#pragma warning restore CS0618 + return; + }} + if (string.Equals(context.FunctionDefinition.EntryPoint, ""TestProject.TestProject.FooStatic"", StringComparison.Ordinal)) + {{ +#pragma warning disable CS0618 + context.GetInvocationResult().Value = global::TestProject.TestProject.FooStatic((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]); +#pragma warning restore CS0618 + return; + }} + }} + }} +{GetExpectedExtensionMethodCode()} +}}".Replace("'", "\""); + + await TestHelpers.RunTestAsync( + _referencedAssemblies, + inputSourceCode, + Constants.FileNames.GeneratedFunctionExecutor, + expectedOutput, + languageVersion: languageVersion); + } } } diff --git a/test/Sdk.Generator.Tests/FunctionExecutor/NotGeneratedTests.cs b/test/Sdk.Generator.Tests/FunctionExecutor/NotGeneratedTests.cs index fe8c2ea26..eb9bfd74a 100644 --- a/test/Sdk.Generator.Tests/FunctionExecutor/NotGeneratedTests.cs +++ b/test/Sdk.Generator.Tests/FunctionExecutor/NotGeneratedTests.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; @@ -12,7 +13,7 @@ using Microsoft.Extensions.Logging; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionExecutor.Tests { public partial class FunctionExecutorGeneratorTests { diff --git a/test/FunctionMetadataGeneratorTests/CustomAttributeExtensionsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/CustomAttributeExtensionsTests.cs similarity index 98% rename from test/FunctionMetadataGeneratorTests/CustomAttributeExtensionsTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/CustomAttributeExtensionsTests.cs index df544fbbc..87c5f2de8 100644 --- a/test/FunctionMetadataGeneratorTests/CustomAttributeExtensionsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/CustomAttributeExtensionsTests.cs @@ -1,14 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; +using System; using System.Collections.Generic; using System.Reflection; using Microsoft.Azure.Functions.Worker.Sdk; using Mono.Cecil; using Xunit; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { public class CustomAttributeExtensionsTests { @@ -163,7 +163,7 @@ public void Use_JustProperties() { } - [JustProperties(Value = null)] + [JustProperties(Value = null!)] public void Use_JustProperties_Null() { } diff --git a/test/Sdk.Generator.Tests/FunctionMetadata/ExtensionsCsProjGeneratorTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/ExtensionsCsProjGeneratorTests.cs new file mode 100644 index 000000000..e32b1d8b7 --- /dev/null +++ b/test/Sdk.Generator.Tests/FunctionMetadata/ExtensionsCsProjGeneratorTests.cs @@ -0,0 +1,273 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker.Sdk; +using Xunit; + +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests +{ + public sealed class ExtensionsCsProjGeneratorTests : IDisposable + { + private HashSet _directoriesToCleanup = new(); + + public enum FuncVersion + { + V3, + V4, + } + + public void Dispose() + { + foreach (string? directory in _directoriesToCleanup) + { + if (directory is null) + { + continue; + } + + if (Directory.Exists(directory)) + { + Directory.Delete(directory, true); + } + } + + _directoriesToCleanup.Clear(); + } + + [Theory] + [InlineData(FuncVersion.V3)] + [InlineData(FuncVersion.V4)] + public void GetCsProjContent_Succeeds(FuncVersion version) + { + var generator = GetGenerator(version, "TestExtension.csproj"); + string actual = generator.GetCsProjContent().Replace("\r\n", "\n"); + string expected = ExpectedCsproj(version).Replace("\r\n", "\n"); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(FuncVersion.V3)] + [InlineData(FuncVersion.V4)] + public void Generate_IncrementalSupport(FuncVersion version) + { + DateTime RunGenerate(string project, out string contents) + { + _directoriesToCleanup.Add(Path.GetDirectoryName(project)); + var generator = GetGenerator(version, project); + generator.Generate(); + + contents = File.ReadAllText(project); + var csproj = new FileInfo(project); + return csproj.LastWriteTimeUtc; + } + + string project = Path.Combine(Guid.NewGuid().ToString(), "TestExtension.csproj"); + DateTime firstRun = RunGenerate(project, out string first); + DateTime secondRun = RunGenerate(project, out string second); + + Assert.NotEqual(firstRun, secondRun); + Assert.Equal(first, second); + } + + [Fact] + public async Task Generate_Updates() + { + DateTime RunGenerate(string project, IDictionary extensions, out string contents) + { + _directoriesToCleanup.Add(Path.GetDirectoryName(project)); + var generator = GetGenerator(FuncVersion.V4, project, extensions); + generator.Generate(); + + contents = File.ReadAllText(project); + var csproj = new FileInfo(project); + return csproj.LastWriteTimeUtc; + } + + Dictionary extensions = new() + { + { "Microsoft.Azure.WebJobs.Extensions.Storage", "4.0.3" }, + { "Microsoft.Azure.WebJobs.Extensions.Http", "3.0.0" }, + { "Microsoft.Azure.WebJobs.Extensions", "2.0.0" }, + }; + + string project = Path.Combine(Guid.NewGuid().ToString(), "TestExtension.csproj"); + DateTime firstRun = RunGenerate(project, extensions, out string first); + + await Task.Delay(10); // to ensure timestamps progress. + extensions.Remove(extensions.Keys.First()); + DateTime secondRun = RunGenerate(project, extensions, out string second); + + Assert.NotEqual(firstRun.Ticks, secondRun.Ticks); + Assert.NotEqual(first, second); + } + + [Fact] + public async Task Generate_Subdirectory_CreatesAll() + { + DateTime RunGenerate(string project, out string contents) + { + _directoriesToCleanup.Add(Path.GetDirectoryName(project)); + var generator = GetGenerator(FuncVersion.V4, project); + generator.Generate(); + + contents = File.ReadAllText(project); + var csproj = new FileInfo(project); + return csproj.LastWriteTimeUtc; + } + + DateTime earliest = DateTime.UtcNow; + + await Task.Delay(10); + string project = Path.Combine(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "TestExtension.csproj"); + DateTime time = RunGenerate(project, out string contents); + + Assert.True(time.Ticks >= earliest.Ticks, $"expected last write time {time.Ticks} to be greater than {earliest.Ticks}."); + Assert.NotNull(contents); + } + + [Fact] + public async Task Generate_Subdirectory_CreatesPartial() + { + DateTime RunGenerate(string project, out string contents) + { + _directoriesToCleanup.Add(Path.GetDirectoryName(project)); + var generator = GetGenerator(FuncVersion.V4, project); + generator.Generate(); + + contents = File.ReadAllText(project); + var csproj = new FileInfo(project); + return csproj.LastWriteTimeUtc; + } + + DateTime earliest = DateTime.UtcNow; + string parent = Guid.NewGuid().ToString(); + Directory.CreateDirectory(parent); + _directoriesToCleanup.Add(parent); + + await Task.Delay(10); + string project = Path.Combine(parent, Guid.NewGuid().ToString(), "TestExtension.csproj"); + DateTime time = RunGenerate(project, out string contents); + + Assert.True(time.Ticks >= earliest.Ticks, $"expected last write time {time.Ticks} to be greater than {earliest.Ticks}."); + Assert.NotNull(contents); + } + + [Fact] + public async Task Generate_ExistingDirectory_DoesNotOverwrite() + { + DateTime RunGenerate(string project, out string contents) + { + _directoriesToCleanup.Add(Path.GetDirectoryName(project)); + var generator = GetGenerator(FuncVersion.V4, project); + generator.Generate(); + + contents = File.ReadAllText(project); + var csproj = new FileInfo(project); + return csproj.LastWriteTimeUtc; + } + + string parent = Guid.NewGuid().ToString(); + Directory.CreateDirectory(parent); + _directoriesToCleanup.Add(parent); + + string existing = Path.Combine(parent, "existing.txt"); + File.WriteAllText(existing, ""); + DateTime expectedWriteTime = new FileInfo(existing).LastWriteTimeUtc; + + await Task.Delay(10); + string project = Path.Combine(parent, "TestExtension.csproj"); + DateTime time = RunGenerate(project, out string contents); + + Assert.True(time.Ticks >= expectedWriteTime.Ticks, $"expected last write time {time.Ticks} to be greater than {expectedWriteTime.Ticks}."); + Assert.NotNull(contents); + Assert.Equal(expectedWriteTime, new FileInfo(existing).LastWriteTimeUtc); + } + + static ExtensionsCsprojGenerator GetGenerator(FuncVersion version, string outputPath) + { + Dictionary extensions = new() + { + { "Microsoft.Azure.WebJobs.Extensions.Storage", "4.0.3" }, + { "Microsoft.Azure.WebJobs.Extensions.Http", "3.0.0" }, + { "Microsoft.Azure.WebJobs.Extensions", "2.0.0" }, + }; + + return GetGenerator(version, outputPath, extensions); + } + + static ExtensionsCsprojGenerator GetGenerator(FuncVersion version, string outputPath, IDictionary extensions) + { + return version switch + { + FuncVersion.V3 => new ExtensionsCsprojGenerator(extensions, outputPath, "v3", Constants.NetCoreApp, Constants.NetCoreVersion31), + FuncVersion.V4 => new ExtensionsCsprojGenerator(extensions, outputPath, "v4", Constants.NetCoreApp, Constants.NetCoreVersion8), + _ => throw new ArgumentOutOfRangeException(nameof(version)), + }; + } + + private static string ExpectedCsproj(FuncVersion version) + => version switch + { + FuncVersion.V3 => ExpectedCsProjV3(), + FuncVersion.V4 => ExpectedCsProjV4(), + _ => throw new ArgumentOutOfRangeException(nameof(version)), + }; + + private static string ExpectedCsProjV3() + { + return @" + + + netcoreapp3.1 + Microsoft.Azure.Functions.Worker.Extensions + true + + + + + + + + + + + + + + + +"; + } + + private static string ExpectedCsProjV4() + { + return @" + + + net8.0 + Microsoft.Azure.Functions.Worker.Extensions + true + + + + + + + + + + + + + + + +"; + } + } +} diff --git a/test/FunctionMetadataGeneratorTests/ExtensionsMetadataEnhancerTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/ExtensionsMetadataEnhancerTests.cs similarity index 83% rename from test/FunctionMetadataGeneratorTests/ExtensionsMetadataEnhancerTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/ExtensionsMetadataEnhancerTests.cs index 8b8b21402..45cdb3a82 100644 --- a/test/FunctionMetadataGeneratorTests/ExtensionsMetadataEnhancerTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/ExtensionsMetadataEnhancerTests.cs @@ -1,12 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Collections.Generic; +using System.Collections.Generic; +using System.IO; using System.Linq; +using Microsoft.Azure.Functions.Tests; using Microsoft.Azure.Functions.Worker.Sdk; using Xunit; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { public class ExtensionsMetadataEnhancerTests { @@ -37,6 +39,24 @@ public void AddHintPath_DoesNotAdd_WhenAlreadyPresent() ValidateAllEqual(GetBasicReferences_WithPresetHintPath(), extensionsPreset); } + [Fact] + public void GetWebJobsExtensions_FindsExtensions() + { + string assembly = Path.Combine(TestUtility.RepoRoot, "sdk", "FunctionMetadataLoaderExtension", "bin", TestUtility.Config, "netstandard2.0", "Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll"); + var extensions = ExtensionsMetadataEnhancer.GetWebJobsExtensions(assembly); + + ValidateAllEqual( + [ + new ExtensionReference() + { + Name = "Startup", + TypeName = "Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.Startup, Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c", + HintPath = "./.azurefunctions/Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll", + } + ], + extensions); + } + private static void ValidateAllEqual(IEnumerable expected, IEnumerable actual) { Assert.Equal(expected.Count(), actual.Count()); diff --git a/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/FunctionMetadataGeneratorTests.cs similarity index 94% rename from test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/FunctionMetadataGeneratorTests.cs index 335e37022..4cb92467b 100644 --- a/test/FunctionMetadataGeneratorTests/FunctionMetadataGeneratorTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/FunctionMetadataGeneratorTests.cs @@ -1,6 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System; using System.Collections; using System.Collections.Concurrent; @@ -26,18 +29,28 @@ using Mono.Cecil; using Xunit; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { public class FunctionMetadataGeneratorTests { - private static Assembly _thisAssembly = typeof(FunctionMetadataGeneratorTests).Assembly; + private const string MetadataFileDirectory = "."; + private const string MetadataFile = $"{MetadataFileDirectory}/functions.metadata"; + private static readonly Assembly _thisAssembly = typeof(FunctionMetadataGeneratorTests).Assembly; + + public FunctionMetadataGeneratorTests() + { + if (File.Exists(MetadataFile)) + { + File.Delete(MetadataFile); + } + } [Fact] public void BasicHttpFunction() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(BasicHttp)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(BasicHttp)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -73,7 +86,34 @@ void ValidateReturn(ExpandoObject b) }); } - FunctionMetadataJsonWriter.WriteMetadata(functions, "."); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); + } + + [Fact] + public async Task Incremental_WritesOnlyIfChanged() + { + var generator = new FunctionMetadataGenerator(); + var module = ModuleDefinition.ReadModule(_thisAssembly.Location); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(BasicHttp)); + var functions = generator.GenerateFunctionMetadata(typeDef); + + // Simulate an existing functions.metadata file with different content. + File.WriteAllText(MetadataFile, "[]"); // empty json array. + DateTime lastWrite = File.GetLastWriteTimeUtc(MetadataFile); + + await Task.Delay(100); // ensure time passes so timestamp isn't too identical. + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); + string contents = File.ReadAllText(MetadataFile); + + DateTime newWrite = File.GetLastWriteTimeUtc(MetadataFile); + Assert.NotEqual(lastWrite, newWrite); + Assert.NotEqual("[]", contents); // ensure the file was written with new content. + + // write again to test incremental. + await Task.Delay(100); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); + Assert.Equal(newWrite, File.GetLastWriteTimeUtc(MetadataFile)); + Assert.Equal(contents, File.ReadAllText(MetadataFile)); } [Fact] @@ -81,7 +121,7 @@ public void BasicHttpAspNetFunction() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(BasicHttpAspNet)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(BasicHttpAspNet)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -117,7 +157,7 @@ void ValidateReturn(ExpandoObject b) }); } - FunctionMetadataJsonWriter.WriteMetadata(functions, "."); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); } [Fact] @@ -125,7 +165,7 @@ public void BasicHttpFunctionWithNoResponse() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(BasicHttpWithNoResponse)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(BasicHttpWithNoResponse)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -150,7 +190,7 @@ void ValidateTrigger(ExpandoObject b) }); } - FunctionMetadataJsonWriter.WriteMetadata(functions, "."); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); } [Fact] @@ -158,7 +198,7 @@ public void BasicHttpFunctionWithExternalReturnType() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(ExternalType_Return)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(ExternalType_Return)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -194,7 +234,7 @@ void ValidateQueueOutput(ExpandoObject b) }); } - FunctionMetadataJsonWriter.WriteMetadata(functions, "."); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); } [Fact] @@ -202,7 +242,7 @@ public void StorageFunctions() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(Storage)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(Storage)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -300,7 +340,7 @@ public void BlobStorageFunctions_SDKTypeBindings() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_BlobStorage)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(SDKTypeBindings_BlobStorage)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -393,7 +433,7 @@ public void BlobCollectionFunctions_SDKTypeBindings() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_BlobCollection)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(SDKTypeBindings_BlobCollection)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -489,11 +529,11 @@ public void TableFunctions_SDKTypeBindings() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_Table)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(SDKTypeBindings_Table)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; - Assert.Equal(5, functions.Count()); + Assert.Equal(4, functions.Count()); var tableClientFunction = functions.Single(p => p.Name == "TableClientFunction"); @@ -516,13 +556,6 @@ public void TableFunctions_SDKTypeBindings() ValidateFunction(enumerableTableEntityFunction, "EnumerableTableEntityFunction", GetEntryPoint(nameof(SDKTypeBindings_Table), nameof(SDKTypeBindings_Table.EnumerableTableEntityFunction)), b => ValidateTableInput(b)); - - var tableUnsupportedTypeFunction = functions.Single(p => p.Name == "TableUnsupportedTypeFunction"); - - ValidateFunction(tableUnsupportedTypeFunction, "TableUnsupportedTypeFunction", GetEntryPoint(nameof(SDKTypeBindings_Table), nameof(SDKTypeBindings_Table.TableUnsupportedTypeFunction)), - b => ValidateTableInputBypassDeferredBinding(b)); - - var tablePocoFunction = functions.Single(p => p.Name == "TablePocoFunction"); ValidateFunction(tablePocoFunction, "TablePocoFunction", GetEntryPoint(nameof(SDKTypeBindings_Table), nameof(SDKTypeBindings_Table.TablePocoFunction)), @@ -558,7 +591,7 @@ public void TimerFunction() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(Timer)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(Timer)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -586,7 +619,7 @@ public void QueueStorageFunctions_SDKTypeBindings() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_Queue)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(SDKTypeBindings_Queue)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -637,7 +670,7 @@ public void MultiOutput_OnReturnType() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(MultiOutput_ReturnType)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(MultiOutput_ReturnType)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -703,7 +736,7 @@ public void MultiOutput_OnReturnType_WithHttp() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(MultiOutput_ReturnType_Http)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(MultiOutput_ReturnType_Http)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -763,7 +796,7 @@ public void MultiOutput_OnReturnType_WithHttp_AspNet() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(MultiOutput_ReturnType_Http_AspNet)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(MultiOutput_ReturnType_Http_AspNet)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -822,15 +855,15 @@ public void JustHttp_OnReturnTypeProperty() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(ReturnType_JustHttp)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(ReturnType_JustHttp)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; Assert.Single(functions); - var HttpAndQueue = functions.Single(p => p.Name == "JustHtt"); + var HttpAndQueue = functions.Single(p => p.Name == "JustHttp"); - ValidateFunction(HttpAndQueue, "JustHtt", GetEntryPoint(nameof(ReturnType_JustHttp), nameof(ReturnType_JustHttp.Justhtt)), + ValidateFunction(HttpAndQueue, "JustHttp", GetEntryPoint(nameof(ReturnType_JustHttp), nameof(ReturnType_JustHttp.JustHttp)), b => ValidateHttpTrigger(b), b => ValidateHttpOutput(b)); @@ -865,7 +898,7 @@ public void MultiReturn_MultiAttribute_IsValid() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(MultiOutput_ReturnType_MultiAttribute)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(MultiOutput_ReturnType_MultiAttribute)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -925,7 +958,7 @@ public void MultiOutput_OnMethod_Throws() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(MultiOutput_Method)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(MultiOutput_Method)); var exception = Assert.Throws(() => generator.GenerateFunctionMetadata(typeDef)); @@ -937,7 +970,7 @@ public void MultiOutput_OnReturnTypeProperty_Throws() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(MultiReturn_MultiBinding)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(MultiReturn_MultiBinding)); var exception = Assert.Throws(() => generator.GenerateFunctionMetadata(typeDef)); @@ -975,11 +1008,11 @@ public void MultiOutput_OnReturnTypeProperty_Throws() public void CardinalityManyFunctions(string functionName, string entryPoint, bool cardinalityMany, string dataType) { var generator = new FunctionMetadataGenerator(); - var typeDef = TestUtility.GetTypeDefinition(typeof(CardinalityMany)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(CardinalityMany)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; - SdkFunctionMetadata metadata = functions.Where(a => string.Equals(a.Name, functionName, StringComparison.Ordinal)).Single(); + SdkFunctionMetadata metadata = functions.Single(a => string.Equals(a.Name, functionName, StringComparison.Ordinal)); ValidateFunction(metadata, functionName, GetEntryPoint(nameof(CardinalityMany), entryPoint), b => ValidateTrigger(b, cardinalityMany)); @@ -1022,7 +1055,7 @@ void ValidateTrigger(ExpandoObject b, bool many) public void CardinalityMany_WithNotIterableTypeThrows() { var generator = new FunctionMetadataGenerator(); - var typeDef = TestUtility.GetTypeDefinition(typeof(EventHubNotBatched)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(EventHubNotBatched)); var exception = Assert.Throws(() => generator.GenerateFunctionMetadata(typeDef)); Assert.Contains("Function is configured to process events in batches but parameter type is not iterable", exception.Message); @@ -1046,14 +1079,14 @@ public void EnableImplicitRegistration_True() var module = ModuleDefinition.ReadModule(_thisAssembly.Location); // Inject enableImplicitRegistration = true into the constructor - var enableImplicitRegistrationParam = new CustomAttributeArgument(TestUtility.GetTypeDefinition(typeof(bool)), true); + var enableImplicitRegistrationParam = new CustomAttributeArgument(TypeHelpers.GetTypeDefinition(typeof(bool)), true); var extInfo = module.Assembly.CustomAttributes.Single(p => p.AttributeType.FullName == Constants.ExtensionsInformationType); extInfo.ConstructorArguments.Add(enableImplicitRegistrationParam); generator.GenerateFunctionMetadata(module); var extension = generator.Extensions.Single(); - Assert.Equal("SdkTests", extension.Key); + Assert.Equal("Sdk.Generator.Tests", extension.Key); Assert.Equal("1.0.0", extension.Value); } @@ -1064,7 +1097,7 @@ public void EnableImplicitRegistration_False() var module = ModuleDefinition.ReadModule(_thisAssembly.Location); // Inject enableImplicitRegistration = false into the constructor - var enableImplicitRegistrationParam = new CustomAttributeArgument(TestUtility.GetTypeDefinition(typeof(bool)), false); + var enableImplicitRegistrationParam = new CustomAttributeArgument(TypeHelpers.GetTypeDefinition(typeof(bool)), false); var extInfo = module.Assembly.CustomAttributes.Single(p => p.AttributeType.FullName == Constants.ExtensionsInformationType); extInfo.ConstructorArguments.Add(enableImplicitRegistrationParam); @@ -1077,7 +1110,7 @@ public void FunctionWithNoRetryHasNullRetryProperty() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(Storage)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(Storage)); var functions = generator.GenerateFunctionMetadata(typeDef); var queueToBlob = functions.Single(p => p.Name == "QueueToBlobFunction"); @@ -1090,13 +1123,13 @@ public void FunctionWithFixedDelayRetry() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(RetryFunctions)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(RetryFunctions)); var functions = generator.GenerateFunctionMetadata(typeDef); var funcName = "FixedDelayRetryFunction"; var fixedDelayFunction = functions.Single(p => p.Name == funcName); - var retry = fixedDelayFunction.Retry; + var retry = fixedDelayFunction.Retry!; Assert.Equal("fixedDelay", retry.Strategy); Assert.Equal(5, retry.MaxRetryCount); @@ -1104,7 +1137,7 @@ public void FunctionWithFixedDelayRetry() Assert.Null(retry.MinimumInterval); Assert.Null(retry.MaximumInterval); - FunctionMetadataJsonWriter.WriteMetadata(functions, "."); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); } [Fact] @@ -1112,13 +1145,13 @@ public void FunctionWithExponentialBackoffRetry() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(RetryFunctions)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(RetryFunctions)); var functions = generator.GenerateFunctionMetadata(typeDef); var funcName = "ExponentialBackoffRetryFunction"; var fixedDelayFunction = functions.Single(p => p.Name == funcName); - var retry = fixedDelayFunction.Retry; + var retry = fixedDelayFunction.Retry!; Assert.Equal("exponentialBackoff", retry.Strategy); Assert.Equal(5, retry.MaxRetryCount); @@ -1126,7 +1159,7 @@ public void FunctionWithExponentialBackoffRetry() Assert.Equal("00:00:04", retry.MinimumInterval); Assert.Equal("00:15:00", retry.MaximumInterval); - FunctionMetadataJsonWriter.WriteMetadata(functions, "."); + FunctionMetadataJsonWriter.WriteMetadata(functions, MetadataFileDirectory); } [Fact] @@ -1149,13 +1182,13 @@ public void ServiceBus_SDKTypeBindings() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_ServiceBus)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(SDKTypeBindings_ServiceBus)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; Assert.Equal(2, functions.Count()); - var extensionReference = typeof(ServiceBusExtensionStartup).Assembly.GetCustomAttribute(); + var extensionReference = typeof(ServiceBusExtensionStartup).Assembly.GetCustomAttribute()!; AssertDictionary(extensions, new Dictionary { @@ -1204,7 +1237,7 @@ public void EventHubs_SDKTypeBindings() { var generator = new FunctionMetadataGenerator(); var module = ModuleDefinition.ReadModule(_thisAssembly.Location); - var typeDef = TestUtility.GetTypeDefinition(typeof(SDKTypeBindings_EventHubs)); + var typeDef = TypeHelpers.GetTypeDefinition(typeof(SDKTypeBindings_EventHubs)); var functions = generator.GenerateFunctionMetadata(typeDef); var extensions = generator.Extensions; @@ -1279,7 +1312,7 @@ private void ValidateFunction(SdkFunctionMetadata sdkFunctionMetadata, string na private static void AssertExpandoObject(ExpandoObject expando, IDictionary expected) { - var dict = (IDictionary)expando; + var dict = (IDictionary)expando!; AssertDictionary(dict, expected); } @@ -1433,13 +1466,6 @@ public object EnumerableTableEntityFunction( throw new NotImplementedException(); } - [Function("TableUnsupportedTypeFunction")] - public object TableUnsupportedTypeFunction( - [TableInput("tableName")] BinaryData tableInput) - { - throw new NotImplementedException(); - } - [Function("TablePocoFunction")] public object TablePocoFunction( [TableInput("tableName")] Poco tableInput) @@ -1585,8 +1611,8 @@ public MultiReturn QueueToBlob( private class ReturnType_JustHttp { - [Function("JustHtt")] - public JustHttp Justhtt( + [Function("JustHttp")] + public JustHttp JustHttp( [HttpTrigger("get")] string req) { throw new NotImplementedException(); @@ -1626,38 +1652,38 @@ public MultiReturn_MultiAttribute HttpAndQueue( private class MultiReturn { [BlobOutput("container1/hello.txt", Connection = "MyOtherConnection")] - public string blobOutput { get; set; } + public string? blobOutput { get; set; } [QueueOutput("queue2")] - public string queueOutput { get; set; } + public string? queueOutput { get; set; } } private class MultiReturn_Http { [QueueOutput("queue2")] - public string queueOutput { get; set; } + public string? queueOutput { get; set; } - public HttpResponseData httpResponseProp { get; set; } + public HttpResponseData? httpResponseProp { get; set; } } private class MultiReturn_Http_AspNet { [QueueOutput("queue2")] - public string queueOutput { get; set; } + public string? queueOutput { get; set; } [HttpResult] - public IActionResult httpResponseProp { get; set; } + public IActionResult? httpResponseProp { get; set; } } private class MultiReturn_MultiAttribute { [QueueOutput("queue2")] - public string queueOutput { get; set; } + public string? queueOutput { get; set; } [SuppressMessage("Microsoft.Naming", "Foo", Justification = "Bar")] [HttpResult] [CustomTest] - public IActionResult httpResponseProp { get; set; } + public IActionResult? httpResponseProp { get; set; } } private class CustomTestAttribute: Attribute @@ -1669,15 +1695,15 @@ private class MultiReturn_MultiBindingOnProp { [BlobOutput("./result")] [QueueOutput("queue2")] - public string queueOutput { get; set; } + public string? queueOutput { get; set; } [HttpResult] - public IActionResult httpResponseProp { get; set; } + public IActionResult? httpResponseProp { get; set; } } private class JustHttp { - public HttpResponseData httpResponseProp { get; set; } + public HttpResponseData? httpResponseProp { get; set; } } private class Timer diff --git a/test/FunctionMetadataGeneratorTests/StringExtensionsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/StringExtensionsTests.cs similarity index 79% rename from test/FunctionMetadataGeneratorTests/StringExtensionsTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/StringExtensionsTests.cs index 36c50ca71..ebacbfd8d 100644 --- a/test/FunctionMetadataGeneratorTests/StringExtensionsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/StringExtensionsTests.cs @@ -4,7 +4,7 @@ using Microsoft.Azure.Functions.Worker.Sdk; using Xunit; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { public class StringExtensionsTests { @@ -14,7 +14,7 @@ public class StringExtensionsTests [InlineData("http", "http")] [InlineData("", "")] [InlineData(null, null)] - public void ToLowerFirstCharacterWorks(string input, string expectedOutput) + public void ToLowerFirstCharacterWorks(string? input, string? expectedOutput) { var actual = input.ToLowerFirstCharacter(); diff --git a/test/FunctionMetadataGeneratorTests/TestUtility.cs b/test/Sdk.Generator.Tests/FunctionMetadata/TypeHelpers.cs similarity index 64% rename from test/FunctionMetadataGeneratorTests/TestUtility.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/TypeHelpers.cs index 854ba4a08..1a61ca289 100644 --- a/test/FunctionMetadataGeneratorTests/TestUtility.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/TypeHelpers.cs @@ -6,11 +6,11 @@ using System.Linq; using Mono.Cecil; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { - public static class TestUtility + public static class TypeHelpers { - public static MethodDefinition GetMethodDefinition(Type type, string methodName) + public static MethodDefinition? GetMethodDefinition(Type type, string methodName) { return GetTypeDefinition(type).Methods.SingleOrDefault(p => p.Name == methodName); } @@ -18,14 +18,14 @@ public static MethodDefinition GetMethodDefinition(Type type, string methodName) public static TypeDefinition GetTypeDefinition(Type type) { var module = ModuleDefinition.ReadModule(type.Assembly.Location); - return module.GetType(type.FullName.Replace("+", "/")); + return module.GetType(type.FullName!.Replace("+", "/")); } public static IEnumerable GetCustomAttributes(Type type, string methodName, string parameterName) { var methodDef = GetMethodDefinition(type, methodName); - var paramDef = methodDef.Parameters.SingleOrDefault(p => p.Name == parameterName); - return paramDef.CustomAttributes; + var paramDef = methodDef!.Parameters.SingleOrDefault(p => p.Name == parameterName); + return paramDef!.CustomAttributes; } } } diff --git a/test/FunctionMetadataGeneratorTests/ZipDeployTaskTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/ZipDeployTaskTests.cs similarity index 91% rename from test/FunctionMetadataGeneratorTests/ZipDeployTaskTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/ZipDeployTaskTests.cs index e4343e9d7..46a058963 100644 --- a/test/FunctionMetadataGeneratorTests/ZipDeployTaskTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/ZipDeployTaskTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.IO; using System.Linq; using System.Net; @@ -10,12 +13,12 @@ using Moq; using Xunit; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { public class ZipDeployTaskTests { - private static string _testZippedPublishContentsPath; - private const string TestAssemblyToTestZipPath = @"Resources\TestPublishContents.zip"; + private static string? _testZippedPublishContentsPath; + private const string TestAssemblyToTestZipPath = @"Resources/TestPublishContents.zip"; private const string UserAgentName = "functions-core-tools"; private const string UserAgentVersion = "1.0"; @@ -27,7 +30,7 @@ public static string TestZippedPublishContentsPath { string codebase = typeof(ZipDeployTaskTests).Assembly.Location; string assemblyPath = new Uri(codebase, UriKind.Absolute).LocalPath; - string baseDirectory = Path.GetDirectoryName(assemblyPath); + string baseDirectory = Path.GetDirectoryName(assemblyPath)!; _testZippedPublishContentsPath = Path.Combine(baseDirectory, TestAssemblyToTestZipPath); } @@ -61,7 +64,7 @@ public async Task ExecuteZipDeploy_InvalidZipFilePath() [InlineData(null, "sitename", true, "https://sitename.scm.azurewebsites.net/api/publish?RemoteBuild=false")] [InlineData("", "sitename", false, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")] [InlineData("", "sitename", true, "https://sitename.scm.azurewebsites.net/api/publish?RemoteBuild=false")] - public async Task ExecuteZipDeploy_PublishUrlOrSiteNameGiven(string publishUrl, string siteName, bool useBlobContainerDeploy, string expectedZipDeployEndpoint) + public async Task ExecuteZipDeploy_PublishUrlOrSiteNameGiven(string? publishUrl, string? siteName, bool useBlobContainerDeploy, string expectedZipDeployEndpoint) { Action, bool> verifyStep = (client, result) => { @@ -81,7 +84,7 @@ public async Task ExecuteZipDeploy_PublishUrlOrSiteNameGiven(string publishUrl, [InlineData("", "")] [InlineData("", null)] [InlineData(null, "")] - public async Task ExecuteZipDeploy_NeitherPublishUrlNorSiteNameGiven(string publishUrl, string siteName) + public async Task ExecuteZipDeploy_NeitherPublishUrlNorSiteNameGiven(string? publishUrl, string? siteName) { Action, bool> verifyStep = (client, result) => { @@ -129,7 +132,7 @@ public async Task ExecuteZipDeploy_VaryingHttpResponseStatuses( await RunZipDeployAsyncTest("https://sitename.scm.azurewebsites.net", null, UserAgentVersion, useBlobContainerDeploy, responseStatusCode, verifyStep); } - private async Task RunZipDeployAsyncTest(string publishUrl, string siteName, string userAgentVersion, bool useBlobContainerDeploy, HttpStatusCode responseStatusCode, Action, bool> verifyStep) + private async Task RunZipDeployAsyncTest(string? publishUrl, string? siteName, string userAgentVersion, bool useBlobContainerDeploy, HttpStatusCode responseStatusCode, Action, bool> verifyStep) { Mock client = new Mock(); @@ -141,7 +144,7 @@ private async Task RunZipDeployAsyncTest(string publishUrl, string siteName, str byte[] plainAuthBytes = Encoding.ASCII.GetBytes("username:password"); string base64AuthParam = Convert.ToBase64String(plainAuthBytes); - Assert.Equal(base64AuthParam, client.Object.DefaultRequestHeaders.Authorization.Parameter); + Assert.Equal(base64AuthParam, client.Object.DefaultRequestHeaders.Authorization!.Parameter); Assert.Equal("Basic", client.Object.DefaultRequestHeaders.Authorization.Scheme); return Task.FromResult(new HttpResponseMessage(responseStatusCode)); diff --git a/test/FunctionMetadataGeneratorTests/ZipDeploymentStatusTests.cs b/test/Sdk.Generator.Tests/FunctionMetadata/ZipDeploymentStatusTests.cs similarity index 93% rename from test/FunctionMetadataGeneratorTests/ZipDeploymentStatusTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadata/ZipDeploymentStatusTests.cs index 0e0e92cc1..8eb2cccff 100644 --- a/test/FunctionMetadataGeneratorTests/ZipDeploymentStatusTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadata/ZipDeploymentStatusTests.cs @@ -1,17 +1,22 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading; -using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; using Microsoft.NET.Sdk.Functions.Http; using Microsoft.NET.Sdk.Functions.MSBuild.Tasks; using Moq; using Newtonsoft.Json; using Xunit; +using Task = System.Threading.Tasks.Task; -namespace Microsoft.Azure.Functions.SdkTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadata.Tests { public class ZipDeploymentStatusTests { @@ -45,6 +50,8 @@ public class ZipDeploymentStatusTests } }"; + private readonly TaskLoggingHelper _log = new(Mock.Of(), "test"); + [Theory] [InlineData(HttpStatusCode.Forbidden, DeployStatus.Unknown)] [InlineData(HttpStatusCode.NotFound, DeployStatus.Unknown)] @@ -69,7 +76,8 @@ public async Task PollDeploymentStatusTest_ForErrorResponses(HttpStatusCode resp { return Task.FromResult(new HttpResponseMessage(responseStatusCode)); }); - ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client.Object, $"{UserAgentName}/{UserAgentVersion}", null, false); + + ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client.Object, $"{UserAgentName}/{UserAgentVersion}", _log, false); // Act var actualdeployStatus = await deploymentStatus.PollDeploymentStatusAsync(deployUrl, userName, password); @@ -89,7 +97,7 @@ public async Task PollDeploymentStatusTest_ForErrorResponses(HttpStatusCode resp [InlineData(HttpStatusCode.Accepted, "", DeployStatus.Conflict)] [InlineData(HttpStatusCode.OK, null, DeployStatus.Unknown)] [InlineData(HttpStatusCode.Accepted, null, DeployStatus.Unknown)] - public async Task PollDeploymentStatusTest_ForValidResponses(HttpStatusCode responseStatusCode, string statusMessage, DeployStatus expectedDeployStatus) + public async Task PollDeploymentStatusTest_ForValidResponses(HttpStatusCode responseStatusCode, string? statusMessage, DeployStatus expectedDeployStatus) { // Arrange string deployUrl = "https://sitename.scm.azurewebsites.net/DeploymentStatus?Id=knownId"; @@ -119,7 +127,7 @@ public async Task PollDeploymentStatusTest_ForValidResponses(HttpStatusCode resp }; return Task.FromResult(responseMessage); }); - ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client.Object, $"{UserAgentName}/{UserAgentVersion}", null, false); + ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client.Object, $"{UserAgentName}/{UserAgentVersion}", _log, false); // Act var actualdeployStatus = await deploymentStatus.PollDeploymentStatusAsync(deployUrl, userName, password); @@ -154,7 +162,7 @@ public async Task PollDeploymentStatusTest_WithDeploymentSummary_Succeeds() return Task.FromResult(responseMessage); }); - ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client.Object, $"{UserAgentName}/{UserAgentVersion}", null, false); + ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client.Object, $"{UserAgentName}/{UserAgentVersion}", _log, false); // Act var actualdeployStatus = await deploymentStatus.PollDeploymentStatusAsync(deployUrl, userName, password); diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/AmbiguousNamespaceTests.cs similarity index 95% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/AmbiguousNamespaceTests.cs index 207ae5e06..615a8d868 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/AmbiguousNamespaceTests.cs @@ -3,13 +3,14 @@ using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -70,7 +71,7 @@ public static HttpResponseData Run(HttpRequestData req) """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -85,10 +86,10 @@ public static HttpResponseData Run(HttpRequestData req) namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -116,6 +117,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AutoConfigureStartupTypeTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/AutoConfigureStartupTypeTests.cs similarity index 87% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AutoConfigureStartupTypeTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/AutoConfigureStartupTypeTests.cs index 3ca59c1bf..52b39a997 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AutoConfigureStartupTypeTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/AutoConfigureStartupTypeTests.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -74,7 +75,8 @@ public static void HttpTrigger([HttpTrigger(AuthorizationLevel.Admin, "get", "po """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = @$"// + string expectedOutput = $$""" +// using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -86,38 +88,39 @@ public static void HttpTrigger([HttpTrigger(AuthorizationLevel.Admin, "get", "po using Microsoft.Extensions.Hosting; namespace TestProject -{{ +{ /// - /// Custom implementation that returns function metadata definitions for the current worker.""/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider - {{ + { /// public Task> GetFunctionMetadataAsync(string directory) - {{ + { var metadataList = new List(); var Function0RawBindings = new List(); - Function0RawBindings.Add(@""{{""""name"""":""""req"""",""""type"""":""""httpTrigger"""",""""direction"""":""""In"""",""""authLevel"""":""""Admin"""",""""methods"""":[""""get"""",""""post""""],""""route"""":""""/api2""""}}""); - Function0RawBindings.Add(@""{{""""name"""":""""$return"""",""""type"""":""""http"""",""""direction"""":""""Out""""}}""); + Function0RawBindings.Add(@"{""name"":""req"",""type"":""httpTrigger"",""direction"":""In"",""authLevel"":""Admin"",""methods"":[""get"",""post""],""route"":""/api2""}"); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); var Function0 = new DefaultFunctionMetadata - {{ - Language = ""dotnet-isolated"", - Name = ""HttpTrigger"", - EntryPoint = ""FunctionApp.HttpTriggerSimple.HttpTrigger"", + { + Language = "dotnet-isolated", + Name = "HttpTrigger", + EntryPoint = "FunctionApp.HttpTriggerSimple.HttpTrigger", RawBindings = Function0RawBindings, - ScriptFile = ""TestProject.dll"" - }}; + ScriptFile = "TestProject.dll" + }; metadataList.Add(Function0); return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); - }} - }} + } + } -{GetExpectedExtensionMethodCode(includeAutoStartupType: includeAutoStartupType)} -}}"; +{{GetExpectedExtensionMethodCode(includeAutoStartupType: includeAutoStartupType)}} +} +"""; var buildPropertiesDict = new Dictionary() { { Constants.BuildProperties.AutoRegisterGeneratedMetadataProvider, includeAutoStartupType.ToString()} @@ -136,10 +139,11 @@ private static string GetExpectedExtensionMethodCode(bool includeAutoStartupType { if (includeAutoStartupType) { - return """ + return $$""" /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -159,6 +163,7 @@ public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHost /// Auto startup class to register the custom implementation generated for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {{Constants.GeneratedCodeAttribute}} public class FunctionMetadataProviderAutoStartup : global::Microsoft.Azure.Functions.Worker.IAutoConfigureStartup { /// @@ -173,10 +178,11 @@ public void Configure(IHostBuilder hostBuilder) """; } - return """ + return $$""" /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProvider/BindingPropertiesParsingTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/BindingPropertiesParsingTests.cs new file mode 100644 index 000000000..abda775de --- /dev/null +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/BindingPropertiesParsingTests.cs @@ -0,0 +1,266 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; +using Microsoft.Azure.Functions.Worker.Sdk.Generators; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests +{ + public partial class FunctionMetadataProviderGeneratorTests + { + public class BindingPropertiesParsingTests + { + private readonly Assembly[] _referencedExtensionAssemblies; + + public BindingPropertiesParsingTests() + { + var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll"); + var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll"); + var hostingExtension = typeof(HostBuilder).Assembly; + var diExtension = typeof(DefaultServiceProviderFactory).Assembly; + var hostingAbExtension = typeof(IHost).Assembly; + var diAbExtension = typeof(IServiceCollection).Assembly; + var mcpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Mcp.dll"); + var blobExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs.dll"); + + _referencedExtensionAssemblies = new[] + { + abstractionsExtension, + httpExtension, + hostingExtension, + hostingAbExtension, + diExtension, + diAbExtension, + mcpExtension, + blobExtension + }; + } + + [Theory] + [InlineData(LanguageVersion.CSharp7_3)] + [InlineData(LanguageVersion.CSharp8)] + [InlineData(LanguageVersion.CSharp9)] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + [InlineData(LanguageVersion.Latest)] + public async Task BindingPropertiesWithDefaultValueDoeNotCreateDuplicatesWhenSetAsNamedArgument(LanguageVersion languageVersion) + { + string inputCode = """ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Http; + using Microsoft.Azure.Functions.Worker.Extensions.Mcp; + + namespace MyCompany.Task + { + public static class SaveSnippetFunction + { + [Function(nameof(SaveSnippetFunction))] + [BlobOutput("blobPath")] + public static string SaveSnippet( + [McpToolTrigger("someString", "someString")] + ToolInvocationContext context, + [McpToolProperty("someString", "someString", IsRequired = true)] + string name + ) + { + throw new NotImplementedException(); + } + } + } + """; + + string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; + string expectedOutput = $$""" + // + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Text.Json; + using System.Threading.Tasks; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + namespace TestProject + { + /// + /// Custom implementation that returns function metadata definitions for the current worker. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {{Constants.GeneratedCodeAttribute}} + public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider + { + /// + public Task> GetFunctionMetadataAsync(string directory) + { + var metadataList = new List(); + var Function0RawBindings = new List(); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""blob"",""direction"":""Out"",""blobPath"":""blobPath""}"); + Function0RawBindings.Add(@"{""name"":""context"",""type"":""mcpToolTrigger"",""direction"":""In"",""toolName"":""someString"",""description"":""someString""}"); + Function0RawBindings.Add(@"{""name"":""name"",""type"":""mcpToolProperty"",""direction"":""In"",""propertyName"":""someString"",""description"":""someString"",""isRequired"":true,""dataType"":""String""}"); + + var Function0 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "SaveSnippetFunction", + EntryPoint = "MyCompany.Task.SaveSnippetFunction.SaveSnippet", + RawBindings = Function0RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function0); + + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); + } + } + + /// + /// Extension methods to enable registration of the custom implementation generated for the current worker. + /// + {{Constants.GeneratedCodeAttribute}} + public static class WorkerHostBuilderFunctionMetadataProviderExtension + { + /// + /// Adds the GeneratedFunctionMetadataProvider to the service collection. + /// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing. + /// + public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder) + { + builder.ConfigureServices(s => + { + s.AddSingleton(); + }); + return builder; + } + } + } + """; + + await TestHelpers.RunTestAsync( + _referencedExtensionAssemblies, + inputCode, + expectedGeneratedFileName, + expectedOutput, + languageVersion: languageVersion); + } + + [Theory] + [InlineData(LanguageVersion.CSharp7_3)] + [InlineData(LanguageVersion.CSharp8)] + [InlineData(LanguageVersion.CSharp9)] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + [InlineData(LanguageVersion.Latest)] + public async Task BindingPropertyWithDefaultValueIsSet(LanguageVersion languageVersion) + { + string inputCode = """ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Http; + using Microsoft.Azure.Functions.Worker.Extensions.Mcp; + + namespace MyCompany.Task + { + public static class SaveSnippetFunction + { + [Function(nameof(SaveSnippetFunction))] + [BlobOutput("blobPath")] + public static string SaveSnippet( + [McpToolTrigger("someString", "someString")] + ToolInvocationContext context, + [McpToolProperty("someString", "someString")] + string name + ) + { + throw new NotImplementedException(); + } + } + } + """; + + string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; + string expectedOutput = $$""" + // + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Text.Json; + using System.Threading.Tasks; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + namespace TestProject + { + /// + /// Custom implementation that returns function metadata definitions for the current worker. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + {{Constants.GeneratedCodeAttribute}} + public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider + { + /// + public Task> GetFunctionMetadataAsync(string directory) + { + var metadataList = new List(); + var Function0RawBindings = new List(); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""blob"",""direction"":""Out"",""blobPath"":""blobPath""}"); + Function0RawBindings.Add(@"{""name"":""context"",""type"":""mcpToolTrigger"",""direction"":""In"",""toolName"":""someString"",""description"":""someString""}"); + Function0RawBindings.Add(@"{""name"":""name"",""type"":""mcpToolProperty"",""direction"":""In"",""propertyName"":""someString"",""description"":""someString"",""isRequired"":false,""dataType"":""String""}"); + + var Function0 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "SaveSnippetFunction", + EntryPoint = "MyCompany.Task.SaveSnippetFunction.SaveSnippet", + RawBindings = Function0RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function0); + + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); + } + } + + /// + /// Extension methods to enable registration of the custom implementation generated for the current worker. + /// + {{Constants.GeneratedCodeAttribute}} + public static class WorkerHostBuilderFunctionMetadataProviderExtension + { + /// + /// Adds the GeneratedFunctionMetadataProvider to the service collection. + /// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing. + /// + public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder) + { + builder.ConfigureServices(s => + { + s.AddSingleton(); + }); + return builder; + } + } + } + """; + + await TestHelpers.RunTestAsync( + _referencedExtensionAssemblies, + inputCode, + expectedGeneratedFileName, + expectedOutput, + languageVersion: languageVersion); + } + } + } +} diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/DependentAssemblyTest.NetFx.cs similarity index 96% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/DependentAssemblyTest.NetFx.cs index 007b680b7..daaede3a4 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/DependentAssemblyTest.NetFx.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -70,7 +71,7 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -85,10 +86,10 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -142,6 +143,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/DependentAssemblyTest.cs similarity index 97% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/DependentAssemblyTest.cs index 1890d6dd0..a7a505695 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/DependentAssemblyTest.cs @@ -3,12 +3,13 @@ using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -62,7 +63,7 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -77,10 +78,10 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -173,6 +174,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -216,7 +218,7 @@ public static void Main() """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -231,10 +233,10 @@ public static void Main() namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -314,6 +316,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/DiagnosticResultTests.cs similarity index 98% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/DiagnosticResultTests.cs index db66ba29c..c7b685a71 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/DiagnosticResultTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; @@ -12,7 +13,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -24,7 +25,6 @@ public DiagnosticResultTests() { var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll"); var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll"); - var storageExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.dll"); var blobExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs.dll"); var queueExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues.dll"); var loggerExtension = typeof(NullLogger).Assembly; @@ -37,7 +37,6 @@ public DiagnosticResultTests() { abstractionsExtension, httpExtension, - storageExtension, blobExtension, queueExtension, loggerExtension, diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/EventHubsBindingsTests.cs similarity index 97% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/EventHubsBindingsTests.cs index bab3c2c84..a805e1be4 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/EventHubsBindingsTests.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; @@ -12,7 +13,7 @@ using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -98,10 +99,10 @@ public class EventHubsInput namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -132,6 +133,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -232,10 +234,10 @@ IEnumerator IEnumerable.GetEnumerator() namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -266,6 +268,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -324,7 +327,7 @@ public static void EnumerableBinaryInputFunction([EventHubTrigger("test", Con """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -339,10 +342,10 @@ public static void EnumerableBinaryInputFunction([EventHubTrigger("test", Con namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -369,6 +372,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -479,7 +483,7 @@ public class EnumerableStringNestedGenericTestClass : EnumerableStringTestCl """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -494,10 +498,10 @@ public class EnumerableStringNestedGenericTestClass : EnumerableStringTestCl namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -560,6 +564,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -644,7 +649,7 @@ public class EnumerableBinaryNestedTestClass : EnumerableBinaryTestClass """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -659,10 +664,10 @@ public class EnumerableBinaryNestedTestClass : EnumerableBinaryTestClass namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -701,6 +706,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -772,7 +778,7 @@ public class Poco """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -787,10 +793,10 @@ public class Poco namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -829,6 +835,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ExtensionsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/ExtensionsTests.cs similarity index 94% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ExtensionsTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/ExtensionsTests.cs index e489bb673..8323ea49a 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ExtensionsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/ExtensionsTests.cs @@ -4,7 +4,7 @@ using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/HttpTriggerTests.cs similarity index 96% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/HttpTriggerTests.cs index ac0270b37..76aaeae35 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/HttpTriggerTests.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -67,7 +68,7 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -82,10 +83,10 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "g namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -113,6 +114,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -169,7 +171,7 @@ public static void HttpTrigger([HttpTrigger(AuthorizationLevel.Admin, "get", "po string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -184,10 +186,10 @@ public static void HttpTrigger([HttpTrigger(AuthorizationLevel.Admin, "get", "po namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -215,6 +217,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -276,7 +279,7 @@ public class JustHttpResponse string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -291,10 +294,10 @@ public class JustHttpResponse namespace MyCompany.MyProject.MyApp { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -322,6 +325,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -390,7 +394,7 @@ public Task FunctionWithTaskReturnType([HttpTrigger("get")] HttpRequestData req) """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """" + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -405,10 +409,10 @@ public Task FunctionWithTaskReturnType([HttpTrigger("get")] HttpRequestData req) namespace MyCompany.MyProject.MyApp { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -449,6 +453,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -465,7 +470,7 @@ public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHost } } } - """"; + """; // override the namespace value for generated types using msbuild property. var buildPropertiesDict = new Dictionary() { diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/IntegratedTriggersAndBindingsTests.cs similarity index 97% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/IntegratedTriggersAndBindingsTests.cs index d8e922736..67f4fd91d 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/IntegratedTriggersAndBindingsTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; @@ -13,7 +14,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -26,7 +27,6 @@ public IntegratedTriggersAndBindingsTests() // load all extensions used in tests (match extensions tested on E2E app? Or include ALL extensions?) var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll"); var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll"); - var storageExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.dll"); var timerExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Timer.dll"); var blobExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs.dll"); var queueExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues.dll"); @@ -43,7 +43,6 @@ public IntegratedTriggersAndBindingsTests() { abstractionsExtension, httpExtension, - storageExtension, timerExtension, blobExtension, queueExtension, @@ -105,7 +104,7 @@ public class MyOutputTypeNoHttpProp string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -120,10 +119,10 @@ public class MyOutputTypeNoHttpProp namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -165,6 +164,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -245,7 +245,7 @@ public class MyOutputType2 string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -260,10 +260,10 @@ public class MyOutputType2 namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -306,6 +306,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -382,7 +383,7 @@ public class Book """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -397,10 +398,10 @@ public class Book namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -430,6 +431,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -493,7 +495,7 @@ public FakeAttribute(string name) """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -508,10 +510,10 @@ public FakeAttribute(string name) namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -539,6 +541,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -589,7 +592,7 @@ public Task RunTimer([TimerTrigger("0 0 0 * * *", RunOnStartup = false)] object """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -604,10 +607,10 @@ public Task RunTimer([TimerTrigger("0 0 0 * * *", RunOnStartup = false)] object namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -634,6 +637,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -684,7 +688,7 @@ public Task Http([HttpTrigger(AuthorizationLevel.Admin, "get", """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -699,10 +703,10 @@ public Task Http([HttpTrigger(AuthorizationLevel.Admin, "get", namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -730,6 +734,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -789,7 +794,7 @@ public async Task RunAsync2([HttpTrigger(AuthorizationLevel.An """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -804,10 +809,10 @@ public async Task RunAsync2([HttpTrigger(AuthorizationLevel.An namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -861,6 +866,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -922,7 +928,7 @@ public sealed class HttpTriggers """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """" + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -937,10 +943,10 @@ public sealed class HttpTriggers namespace MyCompany.MyProject.MyApp { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -994,6 +1000,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -1010,7 +1017,7 @@ public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHost } } } - """"; + """; // override the namespace value for generated types using msbuild property. var buildPropertiesDict = new Dictionary() { diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/KafkaTests.cs similarity index 95% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/KafkaTests.cs index 48e0c3819..bade20b0f 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/KafkaTests.cs @@ -1,16 +1,16 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -70,7 +70,7 @@ public static string Run([KafkaTrigger("LocalBroker", "stringTopicTenPartitions" """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -85,10 +85,10 @@ public static string Run([KafkaTrigger("LocalBroker", "stringTopicTenPartitions" namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -121,6 +121,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/NestedTypesTest.cs similarity index 96% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/NestedTypesTest.cs index a37b6afb7..bb58b9df0 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/NestedTypesTest.cs @@ -3,13 +3,14 @@ using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -73,7 +74,7 @@ public static HttpResponseData Run(HttpRequestData req) """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -88,10 +89,10 @@ public static HttpResponseData Run(HttpRequestData req) namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -119,6 +120,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -177,7 +179,7 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.User, "get")] """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -192,10 +194,10 @@ public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.User, "get")] namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -223,6 +225,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NotGeneratedTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/NotGeneratedTests.cs similarity index 95% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NotGeneratedTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/NotGeneratedTests.cs index 33133b38e..1b98fc62b 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NotGeneratedTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/NotGeneratedTests.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; @@ -12,7 +13,7 @@ using Microsoft.Extensions.Logging; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/RetryOptionsTests.cs similarity index 95% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/RetryOptionsTests.cs index 0046b717b..f201634cf 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/RetryOptionsTests.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System.Reflection; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using System.Threading.Tasks; using Xunit; @@ -9,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -26,7 +27,6 @@ public RetryOptionsTests() var diExtension = typeof(DefaultServiceProviderFactory).Assembly; var hostingAbExtension = typeof(IHost).Assembly; var diAbExtension = typeof(IServiceCollection).Assembly; - var cosmosDBExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.CosmosDB.dll"); var timerExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Timer.dll"); @@ -38,8 +38,7 @@ public RetryOptionsTests() hostingAbExtension, diExtension, diAbExtension, - timerExtension, - cosmosDBExtension + timerExtension }; } @@ -74,7 +73,7 @@ public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -89,10 +88,10 @@ public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -124,6 +123,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -180,7 +180,7 @@ public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -195,10 +195,10 @@ public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -231,6 +231,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/ServiceBustTests.cs similarity index 93% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/ServiceBustTests.cs index c4f7d3c61..ee22eb7cd 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/ServiceBustTests.cs @@ -1,16 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + using System.Reflection; -using System.Text; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -77,7 +77,7 @@ public static void Run([ServiceBusTrigger("queue", Connection = "ServiceBusConne string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -92,10 +92,10 @@ public static void Run([ServiceBusTrigger("queue", Connection = "ServiceBusConne namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -122,6 +122,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/SignalRTest.cs similarity index 95% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/SignalRTest.cs index e11cb3d83..478a5452d 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/SignalRTest.cs @@ -1,16 +1,16 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -66,7 +66,7 @@ public string Negotiate([HttpTrigger(AuthorizationLevel.Function)] HttpRequestDa """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -81,10 +81,10 @@ public string Negotiate([HttpTrigger(AuthorizationLevel.Function)] HttpRequestDa namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -113,6 +113,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProvider/StorageBindingTests.cs similarity index 96% rename from test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs rename to test/Sdk.Generator.Tests/FunctionMetadataProvider/StorageBindingTests.cs index 7cd5d4248..e4e3652b3 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProvider/StorageBindingTests.cs @@ -3,13 +3,14 @@ using System.Reflection; using System.Threading.Tasks; +using Microsoft.Azure.Functions.Sdk.Generator.Tests; using Microsoft.Azure.Functions.Worker.Sdk.Generators; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests { public partial class FunctionMetadataProviderGeneratorTests { @@ -22,7 +23,6 @@ public StorageBindingTests() // load all extensions used in tests (match extensions tested on E2E app? Or include ALL extensions?) var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll"); var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll"); - var storageExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.dll"); var queueExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues.dll"); var hostingExtension = typeof(HostBuilder).Assembly; var diExtension = typeof(DefaultServiceProviderFactory).Assembly; @@ -35,7 +35,6 @@ public StorageBindingTests() abstractionsExtension, blobExtension, httpExtension, - storageExtension, queueExtension, hostingExtension, hostingAbExtension, @@ -76,7 +75,7 @@ public string QueueTriggerAndOutputFunction([QueueTrigger("test-input-dotnet-iso """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -91,10 +90,10 @@ public string QueueTriggerAndOutputFunction([QueueTrigger("test-input-dotnet-iso namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -122,6 +121,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -198,7 +198,7 @@ public object BlobsToQueue( """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -213,10 +213,10 @@ public object BlobsToQueue( namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -270,6 +270,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// @@ -329,7 +330,7 @@ public string Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] Http """; string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; - string expectedOutput = """ + string expectedOutput = $$""" // using System; using System.Collections.Generic; @@ -344,10 +345,10 @@ public string Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] Http namespace TestProject { /// - /// Custom implementation that returns function metadata definitions for the current worker."/> + /// Custom implementation that returns function metadata definitions for the current worker. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + {{Constants.GeneratedCodeAttribute}} public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { /// @@ -375,6 +376,7 @@ public Task> GetFunctionMetadataAsync(string d /// /// Extension methods to enable registration of the custom implementation generated for the current worker. /// + {{Constants.GeneratedCodeAttribute}} public static class WorkerHostBuilderFunctionMetadataProviderExtension { /// diff --git a/test/FunctionMetadataGeneratorTests/Properties/AssemblyInfo.cs b/test/Sdk.Generator.Tests/Properties/AssemblyInfo.cs similarity index 77% rename from test/FunctionMetadataGeneratorTests/Properties/AssemblyInfo.cs rename to test/Sdk.Generator.Tests/Properties/AssemblyInfo.cs index 343971321..cc01840b4 100644 --- a/test/FunctionMetadataGeneratorTests/Properties/AssemblyInfo.cs +++ b/test/Sdk.Generator.Tests/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("SdkTests", "1.0.0")] +[assembly: ExtensionInformation("Sdk.Generator.Tests", "1.0.0")] diff --git a/test/FunctionMetadataGeneratorTests/Resources/TestPublishContents.zip b/test/Sdk.Generator.Tests/Resources/TestPublishContents.zip similarity index 100% rename from test/FunctionMetadataGeneratorTests/Resources/TestPublishContents.zip rename to test/Sdk.Generator.Tests/Resources/TestPublishContents.zip diff --git a/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj b/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj index 538eb8d69..4e79e725e 100644 --- a/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj +++ b/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj @@ -3,8 +3,8 @@ net8.0 latest - Microsoft.Azure.Functions.SdkGeneratorTests - Microsoft.Azure.Functions.SdkGeneratorTests + Microsoft.Azure.Functions.Sdk.Generator.Tests + Microsoft.Azure.Functions.Sdk.Generator.Tests true ..\..\key.snk enable @@ -16,49 +16,57 @@ - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all + + + + + + + + + + + + + + + + + + + + + + - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - + + - - - - - - - - - - - + + + + + + \ No newline at end of file diff --git a/test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs b/test/Sdk.Generator.Tests/SymbolExtensionsTest.cs similarity index 98% rename from test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs rename to test/Sdk.Generator.Tests/SymbolExtensionsTest.cs index 0eb389a99..9acd3a0e9 100644 --- a/test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs +++ b/test/Sdk.Generator.Tests/SymbolExtensionsTest.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Xunit; -namespace Microsoft.Azure.Functions.SdkGeneratorTests.SymbolExtensionsTest +namespace Microsoft.Azure.Functions.Sdk.Generator.Tests { public class SymbolExtensionsTest { diff --git a/test/Sdk.Generator.Tests/TestHelpers.cs b/test/Sdk.Generator.Tests/TestHelpers.cs index 6562f3d68..9a3dabe5e 100644 --- a/test/Sdk.Generator.Tests/TestHelpers.cs +++ b/test/Sdk.Generator.Tests/TestHelpers.cs @@ -18,7 +18,7 @@ using System.Linq; using System.Runtime.Versioning; -namespace Microsoft.Azure.Functions.SdkGeneratorTests +namespace Microsoft.Azure.Functions.Sdk.Generator.Tests { static class TestHelpers { diff --git a/test/TestUtility/ExternalPoco.cs b/test/TestUtility/ExternalPoco.cs index 25331ae4b..0ca66c32c 100644 --- a/test/TestUtility/ExternalPoco.cs +++ b/test/TestUtility/ExternalPoco.cs @@ -1,10 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Text; - namespace Microsoft.Azure.Functions.Tests { /// diff --git a/test/TestUtility/Properties/AssemblyInfo.cs b/test/TestUtility/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3acc9faac --- /dev/null +++ b/test/TestUtility/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/test/DotNetWorkerTests/TestBindingMetadata.cs b/test/TestUtility/TestBindingMetadata.cs similarity index 100% rename from test/DotNetWorkerTests/TestBindingMetadata.cs rename to test/TestUtility/TestBindingMetadata.cs diff --git a/test/DotNetWorker.OpenTelemetry.Tests/TestFunctionBindingsFeature.cs b/test/TestUtility/TestFunctionBindingsFeature.cs similarity index 95% rename from test/DotNetWorker.OpenTelemetry.Tests/TestFunctionBindingsFeature.cs rename to test/TestUtility/TestFunctionBindingsFeature.cs index f1b84c7de..659db24a8 100644 --- a/test/DotNetWorker.OpenTelemetry.Tests/TestFunctionBindingsFeature.cs +++ b/test/TestUtility/TestFunctionBindingsFeature.cs @@ -6,7 +6,7 @@ using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.OutputBindings; -namespace DotNetWorker.OpenTelemetry.Tests +namespace Microsoft.Azure.Functions.Worker.Tests { internal class TestFunctionBindingsFeature : IFunctionBindingsFeature { diff --git a/test/DotNetWorkerTests/TestFunctionContext.cs b/test/TestUtility/TestFunctionContext.cs similarity index 95% rename from test/DotNetWorkerTests/TestFunctionContext.cs rename to test/TestUtility/TestFunctionContext.cs index 731885dca..636cc6212 100644 --- a/test/DotNetWorkerTests/TestFunctionContext.cs +++ b/test/TestUtility/TestFunctionContext.cs @@ -9,12 +9,11 @@ using Azure.Core.Serialization; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.OutputBindings; -using Microsoft.Azure.Functions.Worker.Tests.Features; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Azure.Functions.Worker.Tests { - internal class TestAsyncFunctionContext : TestFunctionContext, IAsyncDisposable + public class TestAsyncFunctionContext : TestFunctionContext, IAsyncDisposable { public TestAsyncFunctionContext() : base(new TestFunctionDefinition(), new TestFunctionInvocation(), CancellationToken.None) @@ -33,7 +32,7 @@ public ValueTask DisposeAsync() } } - internal class TestFunctionContext : FunctionContext, IDisposable + public class TestFunctionContext : FunctionContext, IDisposable { private readonly FunctionInvocation _invocation; private readonly CancellationToken _cancellationToken; diff --git a/test/DotNetWorkerTests/TestFunctionDefinition.cs b/test/TestUtility/TestFunctionDefinition.cs similarity index 100% rename from test/DotNetWorkerTests/TestFunctionDefinition.cs rename to test/TestUtility/TestFunctionDefinition.cs diff --git a/test/DotNetWorkerTests/TestFunctionInvocation.cs b/test/TestUtility/TestFunctionInvocation.cs similarity index 57% rename from test/DotNetWorkerTests/TestFunctionInvocation.cs rename to test/TestUtility/TestFunctionInvocation.cs index 16a7565d0..85b4ffe42 100644 --- a/test/DotNetWorkerTests/TestFunctionInvocation.cs +++ b/test/TestUtility/TestFunctionInvocation.cs @@ -1,8 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; +using Microsoft.Azure.Functions.Worker.Diagnostics; namespace Microsoft.Azure.Functions.Worker.Tests { @@ -20,9 +22,15 @@ public TestFunctionInvocation(string id = null, string functionId = null) FunctionId = functionId; } - // create/dispose activity to pull off it's Id - using Activity activity = new Activity(string.Empty).Start(); - TraceContext = new DefaultTraceContext(activity.Id, Guid.NewGuid().ToString()); + // create/dispose activity to pull off its ID. + using Activity activity = new Activity("Test").Start(); + Dictionary attributes = new Dictionary + { + { TraceConstants.InternalKeys.FunctionInvocationId, Guid.NewGuid().ToString() }, + { TraceConstants.InternalKeys.AzFuncLiveLogsSessionId, Guid.NewGuid().ToString() }, + }; + + TraceContext = new DefaultTraceContext(activity.Id, Guid.NewGuid().ToString(), attributes); } public override string Id { get; } = Guid.NewGuid().ToString(); diff --git a/test/TestUtility/TestUtility.cs b/test/TestUtility/TestUtility.cs index dd4eb98ba..cf5df14e3 100644 --- a/test/TestUtility/TestUtility.cs +++ b/test/TestUtility/TestUtility.cs @@ -11,6 +11,25 @@ namespace Microsoft.Azure.Functions.Tests { public static class TestUtility { +#if DEBUG + public static readonly string Config = "Debug"; +#else + public static readonly string Config = "Release"; +#endif + + public static readonly string RepoRoot = GetDirectoryOfFileAbove(".reporoot"); + + public static string GetDirectoryOfFileAbove(string fileName) + { + string current = Directory.GetCurrentDirectory(); + while (!File.Exists(Path.Combine(current, fileName))) + { + current = Directory.GetParent(current).FullName; + } + + return current; + } + public static IConfiguration GetTestConfiguration() { return new ConfigurationBuilder() diff --git a/test/TestUtility/TestUtility.csproj b/test/TestUtility/TestUtility.csproj index 0cdd9efb7..482a2a7a5 100644 --- a/test/TestUtility/TestUtility.csproj +++ b/test/TestUtility/TestUtility.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Microsoft.Azure.Functions.Tests.TestUtility Microsoft.Azure.Functions.Tests.TestUtility disable @@ -10,11 +10,16 @@ - - - - - + + + + + + + + + + diff --git a/test/Worker.ApplicationInsights.Tests/Worker.ApplicationInsights.Tests.csproj b/test/Worker.ApplicationInsights.Tests/Worker.ApplicationInsights.Tests.csproj index 436cff015..6574cc2a5 100644 --- a/test/Worker.ApplicationInsights.Tests/Worker.ApplicationInsights.Tests.csproj +++ b/test/Worker.ApplicationInsights.Tests/Worker.ApplicationInsights.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests Microsoft.Azure.Functions.Worker.ApplicationInsights.Tests @@ -20,10 +20,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj b/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj index af13311c8..a32cdf765 100644 --- a/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj +++ b/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj @@ -1,7 +1,7 @@  - net48;net7.0 + net48;net8.0 false true true @@ -11,10 +11,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Worker.Extensions.Sample-IncorrectImplementation/Worker.Extensions.Sample-IncorrectImplementation.csproj b/test/Worker.Extensions.Sample-IncorrectImplementation/Worker.Extensions.Sample-IncorrectImplementation.csproj index 2cd2eacff..180a7b37d 100644 --- a/test/Worker.Extensions.Sample-IncorrectImplementation/Worker.Extensions.Sample-IncorrectImplementation.csproj +++ b/test/Worker.Extensions.Sample-IncorrectImplementation/Worker.Extensions.Sample-IncorrectImplementation.csproj @@ -7,7 +7,6 @@ - diff --git a/test/Worker.Extensions.Sample/Worker.Extensions.Sample.csproj b/test/Worker.Extensions.Sample/Worker.Extensions.Sample.csproj index 2cd2eacff..180a7b37d 100644 --- a/test/Worker.Extensions.Sample/Worker.Extensions.Sample.csproj +++ b/test/Worker.Extensions.Sample/Worker.Extensions.Sample.csproj @@ -7,7 +7,6 @@ - diff --git a/test/Worker.Extensions.Shared.Tests/Worker.Extensions.Shared.Tests.csproj b/test/Worker.Extensions.Shared.Tests/Worker.Extensions.Shared.Tests.csproj index 8f64870f4..9387c4f4c 100644 --- a/test/Worker.Extensions.Shared.Tests/Worker.Extensions.Shared.Tests.csproj +++ b/test/Worker.Extensions.Shared.Tests/Worker.Extensions.Shared.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable false @@ -9,10 +9,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/test/Worker.Extensions.SignalRService.Tests/Worker.Extensions.SignalRService.Tests.csproj b/test/Worker.Extensions.SignalRService.Tests/Worker.Extensions.SignalRService.Tests.csproj index adb646b0d..5bb626984 100644 --- a/test/Worker.Extensions.SignalRService.Tests/Worker.Extensions.SignalRService.Tests.csproj +++ b/test/Worker.Extensions.SignalRService.Tests/Worker.Extensions.SignalRService.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable false @@ -9,14 +9,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/DotNetWorkerTests/AspNetCore/FunctionsEndpointDataSourceTests.cs b/test/Worker.Extensions.Tests/AspNetCore/FunctionsEndpointDataSourceTests.cs similarity index 93% rename from test/DotNetWorkerTests/AspNetCore/FunctionsEndpointDataSourceTests.cs rename to test/Worker.Extensions.Tests/AspNetCore/FunctionsEndpointDataSourceTests.cs index ebda2b1b6..301d38d96 100644 --- a/test/DotNetWorkerTests/AspNetCore/FunctionsEndpointDataSourceTests.cs +++ b/test/Worker.Extensions.Tests/AspNetCore/FunctionsEndpointDataSourceTests.cs @@ -35,7 +35,7 @@ public void MapHttpFunction(string routePrefix) Assert.Equal("TestFunction", endpoint.DisplayName); Assert.Equal($"{routePrefix}/TestFunction", endpoint.RoutePattern.RawText); - var endpointMetadata = endpoint.Metadata.Single() as HttpMethodMetadata; + var endpointMetadata = endpoint.Metadata.OfType().Single(); Assert.Equal(new[] { "GET", "POST" }, endpointMetadata.HttpMethods); } @@ -64,7 +64,7 @@ public void MapHttpFunction_CustomRoute() Assert.Equal("TestFunction", endpoint.DisplayName); Assert.Equal($"api/customRoute/function", endpoint.RoutePattern.RawText); - var endpointMetadata = endpoint.Metadata.Single() as HttpMethodMetadata; + var endpointMetadata = endpoint.Metadata.OfType().Single(); Assert.Equal(new[] { "GET", "POST" }, endpointMetadata.HttpMethods); } @@ -93,7 +93,7 @@ public void MapHttpFunction_CustomRoute_CaseInsensitive() Assert.Equal("TestFunction", endpoint.DisplayName); Assert.Equal($"api/customRoute/function", endpoint.RoutePattern.RawText); - var endpointMetadata = endpoint.Metadata.Single() as HttpMethodMetadata; + var endpointMetadata = endpoint.Metadata.OfType().Single(); Assert.Equal(new[] { "GET", "POST" }, endpointMetadata.HttpMethods); } @@ -121,7 +121,7 @@ public void MapHttpFunction_CustomRoute_NoHttpMethodSpecified() Assert.Equal("TestFunction", endpoint.DisplayName); Assert.Equal($"api/customRoute/function", endpoint.RoutePattern.RawText); - var endpointMetadata = endpoint.Metadata.Single() as HttpMethodMetadata; + var endpointMetadata = endpoint.Metadata.OfType().Single(); Assert.Equal([], endpointMetadata.HttpMethods); } diff --git a/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs b/test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs similarity index 92% rename from test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs rename to test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs index 634cdde6d..35ed6e4f4 100644 --- a/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs +++ b/test/Worker.Extensions.Tests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -16,7 +17,7 @@ using Xunit; namespace Microsoft.Azure.Functions.Worker.Tests.AspNetCore -{ +{ public class FunctionsHttpProxyingMiddlewareTests { [Fact] @@ -168,6 +169,24 @@ public async Task InvocationResultNull_WhenResultIsTypeAspNetCoreHttpResponseDat test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Once()); } + [Fact] + public async Task HttpResultOutputBindingNull_WhenUsingAspNetCoreHttpResponseDataInMultiOutputBinding() + { + var test = SetupTest("httpTrigger", GetMultiOutputTypeOutputBindings()); + var mockDelegate = new Mock(); + + SetUpAspNetCoreHttpResponseDataBindingInfo(test.FunctionContext, false); + + var funcMiddleware = new FunctionsHttpProxyingMiddleware(test.MockCoordinator.Object); + await funcMiddleware.Invoke(test.FunctionContext, mockDelegate.Object); + + var httpOutputBinding = test.FunctionContext.GetOutputBindings() + .FirstOrDefault(a => string.Equals(a.BindingType, "http", StringComparison.OrdinalIgnoreCase)); + + Assert.Null(httpOutputBinding.Value); + test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Once()); + } + private static (FunctionContext FunctionContext, HttpContext HttpContext, Mock MockCoordinator) SetupTest(string triggerType, IDictionary outputBindings = null) { var inputBindings = new Dictionary() @@ -189,7 +208,7 @@ private static (FunctionContext FunctionContext, HttpContext HttpContext, Mock(); mockCoordinator diff --git a/test/DotNetWorkerTests/AspNetCore/WorkerRequestServicesMiddlewareTests.cs b/test/Worker.Extensions.Tests/AspNetCore/WorkerRequestServicesMiddlewareTests.cs similarity index 95% rename from test/DotNetWorkerTests/AspNetCore/WorkerRequestServicesMiddlewareTests.cs rename to test/Worker.Extensions.Tests/AspNetCore/WorkerRequestServicesMiddlewareTests.cs index 7f9fbed81..070aed046 100644 --- a/test/DotNetWorkerTests/AspNetCore/WorkerRequestServicesMiddlewareTests.cs +++ b/test/Worker.Extensions.Tests/AspNetCore/WorkerRequestServicesMiddlewareTests.cs @@ -38,7 +38,7 @@ public async Task ServiceProviders_Equal() serviceProvider: provider); var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add(Constants.CorrelationHeader, functionContext.InvocationId); + httpContext.Request.Headers.Append(Constants.CorrelationHeader, functionContext.InvocationId); var httpCoordinator = new Mock(); httpCoordinator diff --git a/test/Worker.Extensions.Tests/Blob/StreamTests.cs b/test/Worker.Extensions.Tests/Blob/StreamTests.cs index d7713f368..8a745326c 100644 --- a/test/Worker.Extensions.Tests/Blob/StreamTests.cs +++ b/test/Worker.Extensions.Tests/Blob/StreamTests.cs @@ -246,7 +246,7 @@ public async Task ConvertAsync_StreamCollection_WithFilePath_Throws_ReturnsFaile // Assert Assert.Equal(ConversionStatus.Failed, conversionResult.Status); Assert.IsType(conversionResult.Error); - Assert.Contains("Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported.", conversionResult.Error.Message); + Assert.Contains("Deserialization of interface or abstract types is not supported.", conversionResult.Error.Message); } } } diff --git a/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs b/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs index 42e11050b..593ad8ef9 100644 --- a/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs +++ b/test/Worker.Extensions.Tests/Cosmos/CosmosDBConverterTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -359,14 +360,18 @@ public async Task ConvertAsync_CosmosContainerIsNull_ThrowsException_ReturnsFail Assert.Equal($"Unable to create Cosmos container client for 'myContainer'.", conversionResult.Error.Message); } - [Fact] - public async Task ConvertAsync_POCO_IdProvided_StatusNot200_ThrowsException_ReturnsFailure() + [Theory] + [InlineData(HttpStatusCode.Conflict)] + [InlineData(HttpStatusCode.Forbidden)] + [InlineData(HttpStatusCode.InternalServerError)] + [InlineData(HttpStatusCode.BadRequest)] + public async Task ConvertAsync_POCO_IdProvided_NotSuccessStatus_ThrowsException_ReturnsFailure(HttpStatusCode httpStatusCode) { object grpcModelBindingData = GrpcTestHelper.GetTestGrpcModelBindingData(GetTestBinaryData(id: "1", partitionKey: "1"), "CosmosDB"); var context = new TestConverterContext(typeof(ToDoItem), grpcModelBindingData); var mockResponse = new Mock(); - var cosmosException = new CosmosException("test failure", System.Net.HttpStatusCode.NotFound, 0, "test", 0); + var cosmosException = new CosmosException("test failure", httpStatusCode, 0, "test", 0); mockResponse.Setup(x => x.EnsureSuccessStatusCode()).Throws(cosmosException); var mockContainer = new Mock(); @@ -384,6 +389,32 @@ public async Task ConvertAsync_POCO_IdProvided_StatusNot200_ThrowsException_Retu Assert.Equal("test failure", conversionResult.Error.Message); } + [Fact] + public async Task ConvertAsync_POCO_IdProvided_Status404_ReturnsSuccess() + { + object grpcModelBindingData = GrpcTestHelper.GetTestGrpcModelBindingData(GetTestBinaryData(id: "1", partitionKey: "1"), "CosmosDB"); + var context = new TestConverterContext(typeof(ToDoItem), grpcModelBindingData); + + var mockResponse = new Mock(); + mockResponse.Setup(x => x.IsSuccessStatusCode).Returns(false); + var cosmosException = new CosmosException("test failure", HttpStatusCode.NotFound, 0, "test", 0); + mockResponse.Setup(x => x.EnsureSuccessStatusCode()).Throws(cosmosException); + + var mockContainer = new Mock(); + mockContainer + .Setup(m => m.ReadItemStreamAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(mockResponse.Object); + + _mockCosmosClient + .Setup(m => m.GetContainer(It.IsAny(), It.IsAny())) + .Returns(mockContainer.Object); + + var conversionResult = await _cosmosDBConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + Assert.Null(conversionResult.Value); + } + private BinaryData GetTestBinaryData(string db = "testDb", string container = "testContainer", string connection = "cosmosConnection", string id = "", string partitionKey = "", string query = "", string location = "", string queryParams = "{}") { string jsonData = $@"{{ diff --git a/test/Worker.Extensions.Tests/EventHubs/EventDataConverterTests.cs b/test/Worker.Extensions.Tests/EventHubs/EventDataConverterTests.cs index 3f7c90f23..37e9dcce4 100644 --- a/test/Worker.Extensions.Tests/EventHubs/EventDataConverterTests.cs +++ b/test/Worker.Extensions.Tests/EventHubs/EventDataConverterTests.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using Azure.Core.Amqp; using Azure.Messaging.EventHubs; using Google.Protobuf; using Microsoft.Azure.Functions.Worker.Converters; @@ -167,6 +168,39 @@ public async Task ConvertAsync_Batch_ReturnsFailure_WrongSource() Assert.Equal("Unexpected binding source 'some-other-source'. Only 'AzureEventHubsEventData' is supported.", result.Error.Message); } + [Fact] + public async Task ConvertAsync_EnqueuedTime() + { + // Validate bug fixed in Azure.Messaging.EventHubs 5.12.2 + var amqpMessage = new AmqpAnnotatedMessage(AmqpMessageBody.FromValue("abc")); + var enqueuedTime = new DateTimeOffset(2025, 10, 8, 8, 43, 21, TimeSpan.Zero); + amqpMessage.MessageAnnotations.Add("x-opt-enqueued-time", enqueuedTime.UtcDateTime); + + var eventData = new EventData(amqpMessage); + + var data = new GrpcModelBindingData(new ModelBindingData() + { + Version = "1.0", + Source = "AzureEventHubsEventData", + Content = ByteString.CopyFrom(ConvertEventDataToBinaryData(eventData)), + ContentType = Constants.BinaryContentType + }); + + var context = new TestConverterContext(typeof(string), data); + var converter = new EventDataConverter(); + + // Act + var result = await converter.ConvertAsync(context); + + // Assert + Assert.Equal(ConversionStatus.Succeeded, result.Status); + var output = result.Value as EventData; + Assert.NotNull(output); + + // Check enqueued time + Assert.Equal(enqueuedTime.UtcDateTime, output.EnqueuedTime.UtcDateTime); + } + private static void AssertEventData(EventData output) { Assert.Equal("body", output.EventBody.ToString()); @@ -190,4 +224,4 @@ private static BinaryData ConvertEventDataToBinaryData(EventData @event) return @event.GetRawAmqpMessage().ToBytes(); } } -} \ No newline at end of file +} diff --git a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusMessageActionsTests.cs b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusMessageActionsTests.cs index 0edf5612e..49ffdc131 100644 --- a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusMessageActionsTests.cs +++ b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusMessageActionsTests.cs @@ -103,7 +103,7 @@ public async Task PassingNullMessageThrows() await Assert.ThrowsAsync(async () => await messageActions.RenewMessageLockAsync(null)); } - private class MockSettlementClient : Settlement.SettlementClient + internal class MockSettlementClient : Settlement.SettlementClient { private readonly string _lockToken; private readonly ByteString _propertiesToModify; diff --git a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActions.cs b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs similarity index 100% rename from test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActions.cs rename to test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs diff --git a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageConverterTests.cs b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageConverterTests.cs new file mode 100644 index 000000000..c6b8c0d76 --- /dev/null +++ b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageConverterTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Azure.Functions.Worker.Converters; +using static Microsoft.Azure.Functions.Worker.Extensions.Tests.ServiceBusMessageActionsTests; +using Microsoft.Azure.Functions.Worker.Tests.Converters; +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Tests +{ + public class ServiceBusSessionMessageConverterTests + { + internal sealed class TestBindingContext : BindingContext + { + public TestBindingContext(IReadOnlyDictionary input) + { + BindingData = input; + } + + public override IReadOnlyDictionary BindingData { get; } + } + + internal sealed class TestFunctionContext : FunctionContext + { + public TestFunctionContext(BindingContext bindingContext) + { + BindingContext = bindingContext; + } + + public override BindingContext BindingContext { get; } + + public override string InvocationId => throw new NotImplementedException(); + + public override string FunctionId => throw new NotImplementedException(); + + public override TraceContext TraceContext => throw new NotImplementedException(); + + public override RetryContext RetryContext => throw new NotImplementedException(); + + public override IServiceProvider InstanceServices { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public override FunctionDefinition FunctionDefinition => throw new NotImplementedException(); + + public override IDictionary Items { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public override IInvocationFeatures Features => throw new NotImplementedException(); + } + + [Fact] + public async Task ConvertAsync_ReturnsSuccess() + { + var data = "{\"SessionLockedUntil\":\"2024-12-05T21:10:36.1193094+00:00\"}"; + + var bindingDataDictionary = new Dictionary + { + { "SessionId", "test" }, + { "SessionActions", JsonSerializer.Serialize(new + { + SessionLockedUntil = "2024-12-05T21:10:36.1193094+00:00" + }) + } + }; + var context = new TestConverterContext(typeof(ServiceBusSessionMessageActions), data, new TestFunctionContext(new TestBindingContext(bindingDataDictionary))); + var converter = new ServiceBusSessionMessageActionsConverter(new MockSettlementClient("test")); + var result = await converter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, result.Status); + var output = result.Value as ServiceBusSessionMessageActions; + Assert.NotNull(output); + } + + [Fact] + public async Task ConvertAsync_Batch_ForReceivedMessage_ReturnsSuccess() + { + var data = "{\"SessionLockedUntil\":\"2024-12-05T21:10:36.1193094+00:00\"}"; + + IList repeatedField = new List { "test" }; + + var bindingDataDictionary = new Dictionary + { + { "SessionIdArray", repeatedField }, + { "SessionActions", JsonSerializer.Serialize(new + { + SessionLockedUntil = "2024-12-05T21:10:36.1193094+00:00" + }) + } + }; + var context = new TestConverterContext(typeof(ServiceBusSessionMessageActions), data, new TestFunctionContext(new TestBindingContext(bindingDataDictionary))); + var converter = new ServiceBusSessionMessageActionsConverter(new MockSettlementClient("test")); + var result = await converter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, result.Status); + var output = result.Value as ServiceBusSessionMessageActions; + Assert.NotNull(output); + } + + [Fact] + public async Task ConvertAsync_ReturnsFailure_NoSessionId() + { + var data = "{\"SessionLockedUntil\":\"2024-12-05T21:10:36.1193094+00:00\"}"; + + var bindingDataDictionary = new Dictionary + { + { "SessionActions", JsonSerializer.Serialize(new + { + SessionLockedUntil = "2024-12-05T21:10:36.1193094+00:00" + }) + } + }; + var context = new TestConverterContext(typeof(ServiceBusSessionMessageActions), data, new TestFunctionContext(new TestBindingContext(bindingDataDictionary))); + var converter = new ServiceBusSessionMessageActionsConverter(new MockSettlementClient("test")); + var result = await converter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Failed, result.Status); + } + } +} diff --git a/test/Worker.Extensions.Tests/Table/TableEntityConverterTests.cs b/test/Worker.Extensions.Tests/Table/TableEntityConverterTests.cs index 9003ec32b..081606b52 100644 --- a/test/Worker.Extensions.Tests/Table/TableEntityConverterTests.cs +++ b/test/Worker.Extensions.Tests/Table/TableEntityConverterTests.cs @@ -81,7 +81,7 @@ public async Task ConvertAsync_CollectionTableEntity_ReturnsUnhandled() [Fact] public async Task ConvertAsync_BadTableEntity_ReturnsFailed() { - object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetBadEntityBinaryData(), "AzureStorageTables"); + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetEntityWithoutRowKeyBinaryData(), "AzureStorageTables"); var context = new TestConverterContext(typeof(TableEntity), source); var mockResponse = new Mock(); var tableClient = new Mock(); diff --git a/test/Worker.Extensions.Tests/Table/TableEntityEnumerableConverterTests.cs b/test/Worker.Extensions.Tests/Table/TableEntityEnumerableConverterTests.cs index ccc969214..b7c141e73 100644 --- a/test/Worker.Extensions.Tests/Table/TableEntityEnumerableConverterTests.cs +++ b/test/Worker.Extensions.Tests/Table/TableEntityEnumerableConverterTests.cs @@ -87,7 +87,7 @@ public async Task ConvertAsync_CollectionTableEntity_ReturnsSuccess() [Fact] public async Task ConvertAsync_BadTableEntity_ReturnsFailed() { - object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetBadEntityBinaryData(), "AzureStorageTables"); + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetEntityWithoutRowKeyBinaryData(), "AzureStorageTables"); var context = new TestConverterContext(typeof(IEnumerable), source); var mockResponse = new Mock(); var tableClient = new Mock(); diff --git a/test/Worker.Extensions.Tests/Table/TablePocoConverterTests.cs b/test/Worker.Extensions.Tests/Table/TablePocoConverterTests.cs new file mode 100644 index 000000000..a290f91a2 --- /dev/null +++ b/test/Worker.Extensions.Tests/Table/TablePocoConverterTests.cs @@ -0,0 +1,387 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Azure; +using Azure.Core.Serialization; +using Azure.Data.Tables; +using Microsoft.Azure.Functions.Worker.Extensions.Tables.Config; +using Microsoft.Azure.Functions.Worker.Extensions.Tables; +using Microsoft.Azure.Functions.Worker.Extensions.Tables.TypeConverters; +using Microsoft.Azure.Functions.Worker.Converters; +using Microsoft.Azure.Functions.Worker.Tests.Converters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Tests.Table +{ + public class TablePocoConverterTests + { + private TablePocoConverter _tableConverter; + private Mock _mockTableServiceClient; + + public TablePocoConverterTests() + { + var host = new HostBuilder().ConfigureFunctionsWorkerDefaults((WorkerOptions options) => { }).Build(); + var logger = host.Services.GetService>(); + + var workerOptions = host.Services.GetService>(); + + _mockTableServiceClient = new Mock(); + + var mockTableOptions = new Mock(); + mockTableOptions + .Setup(m => m.CreateClient()) + .Returns(_mockTableServiceClient.Object); + + var mockTablesOptionsMonitor = new Mock>(); + mockTablesOptionsMonitor + .Setup(m => m.Get(It.IsAny())) + .Returns(mockTableOptions.Object); + + _tableConverter = new TablePocoConverter(workerOptions, mockTablesOptionsMonitor.Object, logger); + } + + [Theory] + [InlineData(typeof(MyEntity))] + [InlineData(typeof(MyTableEntity))] + public async Task ConvertAsync_SinglePocoEntity_ReturnsSuccess(Type targetType) + { + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(targetType, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + _mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var conversionResult = await _tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + Assert.Equal(targetType, conversionResult.Value.GetType()); + } + + [Theory] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(IEnumerable))] + public async Task ConvertAsync_CollectionPocoEntity_ReturnsSuccess(Type targetType) + { + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetEntityWithoutRowKeyBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(targetType, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + _mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var expectedOutput = Page.FromValues(new List { new TableEntity("partitionKey", "rowKey") }, continuationToken: null, mockResponse.Object); + + tableClient + .Setup(c => c.QueryAsync(It.IsAny(), null, null, default)) + .Returns(AsyncPageable.FromPages(new List> { expectedOutput })); + + var conversionResult = await _tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + } + + [Theory] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(IEnumerable))] + public async Task ConvertAsync_CollectionPocoEntity_WithRowKey_ReturnsSuccess(Type targetType) + { + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(targetType, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + _mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var expectedOutput = Page.FromValues(new List { new TableEntity("partitionKey", "rowKey") }, continuationToken: null, mockResponse.Object); + + tableClient + .Setup(c => c.QueryAsync(It.IsAny(), null, null, default)) + .Returns(AsyncPageable.FromPages(new List> { expectedOutput })); + + var conversionResult = await _tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + } + + [Fact] + public async Task ConvertAsync_ModelBindingData_Null_ReturnsUnhandled() + { + var context = new TestConverterContext(typeof(IEnumerable), null); + + var conversionResult = await _tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Unhandled, conversionResult.Status); + } + + [Fact] + public async Task ConvertAsync_SingleTableEntity_NullTargetType_ReturnsFailed() + { + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(null, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + _mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var conversionResult = await _tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Failed, conversionResult.Status); + } + + [Fact] + public async Task ConvertAsync_MyEntityNewField_NewtonSoftJsonSerializer_ReturnsSuccess() + { + var host = new HostBuilder().ConfigureFunctionsWorkerDefaults((WorkerOptions options) => { options.Serializer = new NewtonsoftJsonObjectSerializer(); }).Build(); + var logger = host.Services.GetService>(); + + var workerOptions = host.Services.GetService>(); + + var mockTableServiceClient = new Mock(); + + var mockTableOptions = new Mock(); + mockTableOptions + .Setup(m => m.CreateClient()) + .Returns(mockTableServiceClient.Object); + + var mockTablesOptionsMonitor = new Mock>(); + mockTablesOptionsMonitor + .Setup(m => m.Get(It.IsAny())) + .Returns(mockTableOptions.Object); + + var tableConverter = new TablePocoConverter(workerOptions, mockTablesOptionsMonitor.Object, logger); + + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityWithNewFieldBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(typeof(MyTableEntityWithField), source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var conversionResult = await tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + Assert.Equal(typeof(MyTableEntityWithField), conversionResult.Value.GetType()); + } + + [Theory] + [InlineData(typeof(MyEntity))] + [InlineData(typeof(MyTableEntity))] + public async Task ConvertAsync_NewtonSoftJsonSerializer_ReturnsSuccess(Type targetType) + { + var host = new HostBuilder().ConfigureFunctionsWorkerDefaults((WorkerOptions options) => { options.Serializer = new NewtonsoftJsonObjectSerializer(); }).Build(); + var logger = host.Services.GetService>(); + + var workerOptions = host.Services.GetService>(); + + var mockTableServiceClient = new Mock(); + + var mockTableOptions = new Mock(); + mockTableOptions + .Setup(m => m.CreateClient()) + .Returns(mockTableServiceClient.Object); + + var mockTablesOptionsMonitor = new Mock>(); + mockTablesOptionsMonitor + .Setup(m => m.Get(It.IsAny())) + .Returns(mockTableOptions.Object); + + var tableConverter = new TablePocoConverter(workerOptions, mockTablesOptionsMonitor.Object, logger); + + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(targetType, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var conversionResult = await tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + Assert.Equal(targetType, conversionResult.Value.GetType()); + } + + [Theory] + [InlineData(typeof(MyEntity))] + [InlineData(typeof(MyTableEntity))] + public async Task ConvertAsync_JsonSerializer_ReturnsSuccess(Type t) + { + var host = new HostBuilder().ConfigureFunctionsWorkerDefaults((WorkerOptions options) => { options.Serializer = new JsonObjectSerializer (); }).Build(); + var logger = host.Services.GetService>(); + + var workerOptions = host.Services.GetService>(); + + var mockTableServiceClient = new Mock(); + + var mockTableOptions = new Mock(); + mockTableOptions + .Setup(m => m.CreateClient()) + .Returns(mockTableServiceClient.Object); + + var mockTablesOptionsMonitor = new Mock>(); + mockTablesOptionsMonitor + .Setup(m => m.Get(It.IsAny())) + .Returns(mockTableOptions.Object); + + var tableConverter = new TablePocoConverter(workerOptions, mockTablesOptionsMonitor.Object, logger); + + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(t, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var conversionResult = await tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + Assert.Equal(t, conversionResult.Value.GetType()); + } + + [Theory] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(IEnumerable))] + public async Task ConvertAsync_CollectionPocoEntity_NewtonSoftSerializer_ReturnsSuccess(Type t) + { + var host = new HostBuilder().ConfigureFunctionsWorkerDefaults((WorkerOptions options) => { options.Serializer = new NewtonsoftJsonObjectSerializer(); }).Build(); + var logger = host.Services.GetService>(); + + var workerOptions = host.Services.GetService>(); + + var mockTableServiceClient = new Mock(); + + var mockTableOptions = new Mock(); + mockTableOptions + .Setup(m => m.CreateClient()) + .Returns(mockTableServiceClient.Object); + + var mockTablesOptionsMonitor = new Mock>(); + mockTablesOptionsMonitor + .Setup(m => m.Get(It.IsAny())) + .Returns(mockTableOptions.Object); + + var tableConverter = new TablePocoConverter(workerOptions, mockTablesOptionsMonitor.Object, logger); + + object source = GrpcTestHelper.GetTestGrpcModelBindingData(TableTestHelper.GetTableEntityBinaryData(), "AzureStorageTables"); + var context = new TestConverterContext(t, source); + var mockResponse = new Mock(); + var tableClient = new Mock(); + + tableClient + .Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), null, default)) + .ReturnsAsync(Response.FromValue(new TableEntity(It.IsAny(), It.IsAny()), mockResponse.Object)); + + mockTableServiceClient + .Setup(c => c.GetTableClient(Constants.TableName)) + .Returns(tableClient.Object); + + var expectedOutput = Page.FromValues(new List { new TableEntity("partitionKey", "rowKey") }, continuationToken: null, mockResponse.Object); + + tableClient + .Setup(c => c.QueryAsync(It.IsAny(), null, null, default)) + .Returns(AsyncPageable.FromPages(new List> { expectedOutput })); + + var conversionResult = await tableConverter.ConvertAsync(context); + + Assert.Equal(ConversionStatus.Succeeded, conversionResult.Status); + } + + class MyEntity + { + public MyEntity() { } + + public MyEntity(string pk, string rk) + { + PartitionKey = pk; + RowKey = rk; + } + + public string PartitionKey { get; set; } + public string RowKey { get; set; } + } + + class MyTableEntity : ITableEntity + { + public MyTableEntity() { } + + public MyTableEntity(string pk, string rk) + { + PartitionKey = pk; + RowKey = rk; + } + + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } + } + + class MyTableEntityWithField : ITableEntity + { + public MyTableEntityWithField() { } + + public MyTableEntityWithField(string pk, string rk) + { + PartitionKey = pk; + RowKey = rk; + NewField = "test"; + } + + public string PartitionKey { get; set; } + public string RowKey { get; set; } + public DateTimeOffset? Timestamp { get; set; } + public ETag ETag { get; set; } + public string NewField { get; set; } + } + } +} diff --git a/test/Worker.Extensions.Tests/Table/TableTestHelper.cs b/test/Worker.Extensions.Tests/Table/TableTestHelper.cs index 623b03800..0cb3d0d77 100644 --- a/test/Worker.Extensions.Tests/Table/TableTestHelper.cs +++ b/test/Worker.Extensions.Tests/Table/TableTestHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; @@ -29,7 +29,7 @@ public static BinaryData GetTableEntityBinaryData() "}"); } - public static BinaryData GetBadEntityBinaryData() + public static BinaryData GetEntityWithoutRowKeyBinaryData() { return new BinaryData("{" + "\"Connection\" : \"Connection\"," + @@ -37,5 +37,16 @@ public static BinaryData GetBadEntityBinaryData() "\"PartitionKey\" : \"PartitionKey\"" + "}"); } + + public static BinaryData GetTableEntityWithNewFieldBinaryData() + { + return new BinaryData("{" + + "\"Connection\" : \"Connection\"," + + "\"TableName\" : \"TableName\"," + + "\"PartitionKey\" : \"PartitionKey\"," + + "\"RowKey\" : \"RowKey\"," + + "\"NewField\" : \"NewField\"" + + "}"); + } } -} \ No newline at end of file +} diff --git a/test/Worker.Extensions.Tests/TestConverterContext.cs b/test/Worker.Extensions.Tests/TestConverterContext.cs new file mode 100644 index 000000000..c1c6d2bc3 --- /dev/null +++ b/test/Worker.Extensions.Tests/TestConverterContext.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker.Converters; + +namespace Microsoft.Azure.Functions.Worker.Tests.Converters +{ + internal class TestConverterContext : ConverterContext + { + public TestConverterContext(Type targetType, object source, FunctionContext context = null) + { + TargetType = targetType; + Source = source; + FunctionContext = context; //TODO: SharedTestFunctionContext ?? new TestFunctionContext(); + } + + public override object Source { get; } + + public override FunctionContext FunctionContext { get; } + + public override Type TargetType { get; } + + public override IReadOnlyDictionary Properties { get; } + } +} diff --git a/test/Worker.Extensions.Tests/Worker.Extensions.Tests.csproj b/test/Worker.Extensions.Tests/Worker.Extensions.Tests.csproj index fffc0de38..50eb07d1b 100644 --- a/test/Worker.Extensions.Tests/Worker.Extensions.Tests.csproj +++ b/test/Worker.Extensions.Tests/Worker.Extensions.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false Microsoft.Azure.Functions.Worker.Extensions.Tests Microsoft.Azure.Functions.Worker.Extensions.Tests @@ -12,10 +12,11 @@ - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -25,10 +26,12 @@ + - + + diff --git a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/AspNetCoreHttpRequestDataTests.cs b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/AspNetCoreHttpRequestDataTests.cs index 908eb4b0e..d793d5bec 100644 --- a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/AspNetCoreHttpRequestDataTests.cs +++ b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/AspNetCoreHttpRequestDataTests.cs @@ -5,61 +5,64 @@ using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore; -using Microsoft.Azure.Functions.Worker.Tests; +//using Microsoft.Azure.Functions.Worker.Tests; namespace Worker.Extensions.Http.AspNetCore.Tests { public class AspNetCoreHttpRequestDataTests { - [Fact] - public void RequestData_ExposesRequestProperties() - { - var uri = "http://localhost:808/test/123?query=test"; - var request = CreateRequest(uri, "POST", "Hello World"); + // Requires fix to share TestFunctionContext + + + //[Fact] + //public void RequestData_ExposesRequestProperties() + //{ + // var uri = "http://localhost:808/test/123?query=test"; + // var request = CreateRequest(uri, "POST", "Hello World"); - var testIdentity = new ClaimsIdentity(); - var testUser = new ClaimsPrincipal(testIdentity); - request.HttpContext.User = testUser; + // var testIdentity = new ClaimsIdentity(); + // var testUser = new ClaimsPrincipal(testIdentity); + // request.HttpContext.User = testUser; - var requestData = new AspNetCoreHttpRequestData(request, new TestFunctionContext()); + // var requestData = new AspNetCoreHttpRequestData(request, new TestFunctionContext()); - Assert.Equal(uri, requestData.Url.AbsoluteUri); - Assert.Same(request.Body, requestData.Body); - Assert.Same(testIdentity, requestData.Identities.Single()); - Assert.Equal(request.Method, requestData.Method); - } + // Assert.Equal(uri, requestData.Url.AbsoluteUri); + // Assert.Same(request.Body, requestData.Body); + // Assert.Same(testIdentity, requestData.Identities.Single()); + // Assert.Equal(request.Method, requestData.Method); + //} - [Fact] - public void RequestData_ExposesRequestCookies() - { - var uri = "http://localhost:808/test/123?query=test"; - var request = CreateRequest(uri, "POST", "Hello World"); - request.Cookies = new CookiesCollection - { - { "cookie1", "value1" }, - { "cookie2", "value2" } - }; + //[Fact] + //public void RequestData_ExposesRequestCookies() + //{ + // var uri = "http://localhost:808/test/123?query=test"; + // var request = CreateRequest(uri, "POST", "Hello World"); + // request.Cookies = new CookiesCollection + // { + // { "cookie1", "value1" }, + // { "cookie2", "value2" } + // }; - var requestData = new AspNetCoreHttpRequestData(request, new TestFunctionContext()); + // var requestData = new AspNetCoreHttpRequestData(request, new TestFunctionContext()); - Assert.Collection(requestData.Cookies, - c => Assert.Equal("cookie1:value1", $"{c.Name}:{c.Value}"), - c => Assert.Equal("cookie2:value2", $"{c.Name}:{c.Value}")); - } + // Assert.Collection(requestData.Cookies, + // c => Assert.Equal("cookie1:value1", $"{c.Name}:{c.Value}"), + // c => Assert.Equal("cookie2:value2", $"{c.Name}:{c.Value}")); + //} - private static HttpRequest CreateRequest(string uri, string method, string body) - { - var request = new DefaultHttpContext().Request; - var uriBuilder = new UriBuilder(uri); + //private static HttpRequest CreateRequest(string uri, string method, string body) + //{ + // var request = new DefaultHttpContext().Request; + // var uriBuilder = new UriBuilder(uri); - request.Scheme = uriBuilder.Scheme; - request.Host = new HostString(uriBuilder.Host, uriBuilder.Port); - request.Path = uriBuilder.Path; - request.Method = method; - request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); - request.QueryString = new QueryString(uriBuilder.Query); + // request.Scheme = uriBuilder.Scheme; + // request.Host = new HostString(uriBuilder.Host, uriBuilder.Port); + // request.Path = uriBuilder.Path; + // request.Method = method; + // request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); + // request.QueryString = new QueryString(uriBuilder.Query); - return request; - } + // return request; + //} } } diff --git a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs index 87603cf34..56552ea59 100644 --- a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs +++ b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs @@ -347,6 +347,179 @@ public class MyOutputType await test.RunAsync(); } + [Fact] + public async Task HttpResultAttribute_WhenReturnTypeIsWrappedInTask_Expected() + { + string testCode = @" + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Http; + + namespace AspNetIntegration + { + public class MultipleOutputBindings + { + [Function(""TaskOfPocoOutput"")] + public Task Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequestData req) + { + throw new System.NotImplementedException(); + } + public class MyOutputType + { + public HttpResponseData Result { get; set; } + + [BlobOutput(""test-samples-output/{name}-output.txt"")] + public string MessageText { get; set; } + } + } + }"; + + var test = new AnalyzerTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = testCode + }; + + test.ExpectedDiagnostics.Add(Verifier.Diagnostic(DiagnosticDescriptors.MultipleOutputWithHttpResponseDataWithoutHttpResultAttribute) + .WithSeverity(DiagnosticSeverity.Warning) + .WithLocation(12, 20) + .WithArguments("\"TaskOfPocoOutput\"")); + + await test.RunAsync(); + } + + [Fact] + public async Task HttpResultAttributeForTaskOfPocoExpected_CodeFixWorks() + { + string inputCode = @" +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace AspNetIntegration +{ + public class MultipleOutputBindings + { + [Function(""TaskOfPocoOutput"")] + public Task Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequestData req) + { + throw new System.NotImplementedException(); + } + public class MyOutputType + { + public HttpResponseData Result { get; set; } + + [BlobOutput(""test-samples-output/{name}-output.txt"")] + public string MessageText { get; set; } + } + } +}"; + + string expectedCode = @" +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace AspNetIntegration +{ + public class MultipleOutputBindings + { + [Function(""TaskOfPocoOutput"")] + public Task Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequestData req) + { + throw new System.NotImplementedException(); + } + public class MyOutputType + { + [HttpResult] + public HttpResponseData Result { get; set; } + + [BlobOutput(""test-samples-output/{name}-output.txt"")] + public string MessageText { get; set; } + } + } +}"; + + var expectedDiagnosticResult = CodeFixVerifier + .Diagnostic("AZFW0016") + .WithSeverity(DiagnosticSeverity.Warning) + .WithLocation(12, 16) + .WithArguments("\"TaskOfPocoOutput\""); + + var test = new CodeFixTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = inputCode, + FixedCode = expectedCode + }; + + test.ExpectedDiagnostics.Add(expectedDiagnosticResult); + await test.RunAsync(); + } + + [Fact] + public async Task HttpTriggerFunctionWithHttpResponseData_NoDiagnostic() + { + string testCode = @" +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Azure.Functions.Worker; + +namespace AspNetIntegration +{ + public class SimpleHttpFunction + { + [Function(""SimpleHttpTaskOutput"")] + public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequestData req) + { + throw new System.NotImplementedException(); + } + } +}"; + + var test = new AnalyzerTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Fact] + public async Task HttpTriggerFunctionWithTaskOfIActionResult_NoDiagnostic() + { + string testCode = @" +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; + +namespace AspNetIntegration +{ + public class SimpleHttpFunction + { + [Function(""SimpleHttpTaskOutput"")] + public Task Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + return Task.FromResult(new OkResult()); + } + } +}"; + + var test = new AnalyzerTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = testCode + }; + + await test.RunAsync(); + } + private static ReferenceAssemblies LoadRequiredDependencyAssemblies() { var referenceAssemblies = ReferenceAssemblies.Net.Net60.WithPackages(ImmutableArray.Create( diff --git a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/Worker.Extensions.Http.AspNetCore.Tests.csproj b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/Worker.Extensions.Http.AspNetCore.Tests.csproj index 271194538..74ea72db1 100644 --- a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/Worker.Extensions.Http.AspNetCore.Tests.csproj +++ b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/Worker.Extensions.Http.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Tests @@ -14,27 +14,26 @@ - + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - \ No newline at end of file diff --git a/test/extensions/Worker.Extensions.Timer.Tests/Worker.Extensions.Timer.Tests.csproj b/test/extensions/Worker.Extensions.Timer.Tests/Worker.Extensions.Timer.Tests.csproj index 8474952b5..2e55a1320 100644 --- a/test/extensions/Worker.Extensions.Timer.Tests/Worker.Extensions.Timer.Tests.csproj +++ b/test/extensions/Worker.Extensions.Timer.Tests/Worker.Extensions.Timer.Tests.csproj @@ -13,10 +13,16 @@ - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tools/devpack.ps1 b/tools/devpack.ps1 index 0167e24e7..166b040b9 100644 --- a/tools/devpack.ps1 +++ b/tools/devpack.ps1 @@ -1,4 +1,8 @@ param( + [Parameter(Mandatory=$false)] + [String] + [ValidateSet("net8", "netfx")] + $DotnetVersion, [Parameter(Mandatory=$false)] [Switch] $E2E, @@ -13,23 +17,30 @@ # Packs the SDK locally, and (by default) updates the Sample to use this package, then builds. # Specify --E2E to instead target the E2E test app. -$buildNumber = "local" + [System.DateTime]::Now.ToString("yyyyMMddHHmm") +# Use metadata to provide a version that is always head of the latest stable package +# E.g: Nuget packages will be named 2.0.0 with the version metedata set to 2.0.0+202501311440-local +$buildNumber = "+" + [System.DateTime]::Now.ToString("yyyyMMddHHmm") Write-Host Write-Host "Building packages with BuildNumber $buildNumber" $rootPath = Split-Path -Parent $PSScriptRoot -$project = "$rootPath/samples/FunctionApp/FunctionApp.csproj" +$projects = @("$rootPath/samples/FunctionApp/FunctionApp.csproj") $sdkProject = "$rootPath/build/DotNetWorker.Core.slnf" if($E2E -eq $true) { - $project = "$rootPath/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj" + # Only add E2EAspNetCoreApp if DotnetVersion is not netfx + if ($DotnetVersion -ne "netfx") { + $projects += "$rootPath/test/E2ETests/E2EApps/E2EAspNetCoreApp/E2EAspNetCoreApp.csproj" + } + # Always add E2EApp project for E2E tests + $projects += "$rootPath/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj" } if ($SkipBuildOnPack -eq $true) { - $AdditionalPackArgs +="--no-build"; + $AdditionalPackArgs += "--no-build" } $localPack = "$rootPath/local" @@ -38,42 +49,44 @@ if (!(Test-Path $localPack)) New-Item -Path $localPack -ItemType directory | Out-Null } Write-Host -Write-Host "---Updating project with local SDK pack---" +Write-Host "---Updating projects with local SDK pack---" Write-Host "Packing Core .NET Worker projects to $localPack" -& "dotnet" "pack" $sdkProject "-p:PackageOutputPath=$localPack" "-nologo" "-p:BuildNumber=$buildNumber" $AdditionalPackArgs +& "dotnet" "pack" $sdkProject "-p:PackageOutputPath=$localPack" "-nologo" "-p:VersionSuffix=$buildNumber" $AdditionalPackArgs Write-Host -Write-Host "Removing SDK package reference in $project" -& "dotnet" "remove" $project "package" "Microsoft.Azure.Functions.Worker.Sdk" -Write-Host +foreach ($project in $projects) { + Write-Host "Removing SDK package reference in $project" + & "dotnet" "remove" $project "package" "Microsoft.Azure.Functions.Worker.Sdk" + Write-Host -Write-Host "Removing Worker package reference in $project" -& "dotnet" "remove" $project "package" "Microsoft.Azure.Functions.Worker" -Write-Host + Write-Host "Removing Worker package reference in $project" + & "dotnet" "remove" $project "package" "Microsoft.Azure.Functions.Worker" + Write-Host -Write-Host "Finding latest local Worker package in $localPack" -$package = Find-Package Microsoft.Azure.Functions.Worker -Source $localPack -$version = $package.Version -Write-Host "Found $version" -Write-Host + Write-Host "Finding latest local Worker package in $localPack" + $package = Find-Package Microsoft.Azure.Functions.Worker -Source $localPack + $version = $package.Version + Write-Host "Found $version" + Write-Host -Write-Host "Adding Worker package version $version to $project" -& "dotnet" "add" $project "package" "Microsoft.Azure.Functions.Worker" "-v" $version "-s" $localPack "-n" -Write-Host + Write-Host "Adding Worker package version $version to $project" + & "dotnet" "add" $project "package" "Microsoft.Azure.Functions.Worker" "-v" $version "-s" $localPack "-n" + Write-Host -Write-Host "Finding latest local SDK package in $localPack" -$package = Find-Package "Microsoft.Azure.Functions.Worker.Sdk" -Source $localPack -$version = $package.Version -Write-Host "Found $version" -Write-Host -Write-Host "Adding SDK package version $version to $project" -& "dotnet" "add" $project "package" "Microsoft.Azure.Functions.Worker.Sdk" "-v" $version "-s" $localPack "-n" -Write-Host -$configFile = Split-Path "$project" -$configFile += "/NuGet.Config" -Write-Host "Config file name" $configFile -& "dotnet" "nuget" "add" "source" $localPack "--name" "local" "--configfile" "$configFile" -Write-Host "Building $project" + Write-Host "Finding latest local SDK package in $localPack" + $package = Find-Package "Microsoft.Azure.Functions.Worker.Sdk" -Source $localPack + $version = $package.Version + Write-Host "Found $version" + Write-Host + Write-Host "Adding SDK package version $version to $project" + & "dotnet" "add" $project "package" "Microsoft.Azure.Functions.Worker.Sdk" "-v" $version "-s" $localPack "-n" + Write-Host + $configFile = Split-Path "$project" + $configFile += "/NuGet.Config" + Write-Host "Config file name" $configFile + & "dotnet" "nuget" "add" "source" $localPack "--name" "local" "--configfile" "$configFile" + Write-Host "Building $project" -& "dotnet" "build" $project "-nologo" "-p:TestBuild=true" -Write-Host "------" + & "dotnet" "build" $project "-nologo" "-p:TestBuild=true" + Write-Host "------" +}