From 6eb8998937876750283fdeb5d75b38020158e5cd Mon Sep 17 00:00:00 2001 From: Jacob Viau Date: Wed, 23 Oct 2024 15:42:09 -0700 Subject: [PATCH] Prep Extensions.Rpc release: merge main into release/main (#2805) * Generators package version update (#2755) * Generator tests: Add transitive dependency for System.Text.Json v8.0.5 & bump extension versions (#2760) * Fixing Function Executor test * Refactor WebJobs extension info (#2762) * skipBuildTagsForGitHubPullRequests when the PR is a fork (#2770) * Bump System.Text.Json from 8.0.4 to 8.0.5 in /host/src/FunctionsNetHost (#2768) Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 8.0.4 to 8.0.5. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.4...v8.0.5) --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * re-use FunctionsWorkerApplicationBuilder if called multiple times (#2774) * Add worker extension validation to CI (#2764) * ignore rider temp files * Support SignalR trigger return value (#2771) * add skipBuildTagsForGitHubPullRequests for extensions (#2779) * Fix typos in CI referencing test projects (#2773) * Adding a null check before initiating the internal Activity (#2765) * Adding a null check for the internal Activity. * Bump System.Text.Json to 8.0.5 (#2783) * Use full namespace for Task.FromResult in function metadata provider generator to avoid namespace conflict (#2681) * Analyzer for Multiple-Output Binding Scenarios with ASP.NET Core Integration (#2706) * Remove documentation tag (#2751) The parameter does not exist. * Update global.json .net8 value (#2795) * initial fix of duplicate registrations if AddFunctionsWorkerCore called twice (#2790) * Ignoring fatal exceptions in InvocationHandler (#2789) * Update nethost global json, update sample (#2797) * Set extension RPC max message length (#2772) * Set max message length for RPC client * Update Rpc version and release notes * Update packages (#2800) --------- Signed-off-by: dependabot[bot] Co-authored-by: Surbhi Gupta Co-authored-by: Lilian Kasem Co-authored-by: Fabio Cavalcante Co-authored-by: sarah <35204912+satvu@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Brett Samblanet Co-authored-by: Simon Cropp Co-authored-by: yzt Co-authored-by: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Co-authored-by: David Lee <10739819+DL444@users.noreply.github.com> Co-authored-by: Jonathan --- .gitignore | 1 + Directory.Build.props | 10 + docs/analyzer-rules/AZFW0014.md | 2 +- docs/analyzer-rules/AZFW0015.md | 44 +++ docs/analyzer-rules/AZFW0016.md | 46 +++ eng/build/WorkerExtensions.targets | 30 ++ .../extensionValidationProjectTemplate.txt | 10 + eng/ci/public-build.yml | 4 + eng/ci/templates/jobs/run-unit-tests.yml | 10 +- extensions/Directory.Build.targets | 8 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 2 - .../src/Worker.Extensions.CosmosDB.csproj | 4 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 1 - .../src/Worker.Extensions.EventGrid.csproj | 4 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 2 - .../src/Worker.Extensions.EventHubs.csproj | 4 + .../CodeFixForHttpResultAttributeExpected.cs | 96 +++++ .../src/DiagnosticDescriptors.cs | 7 + .../HttpResultAttributeExpectedAnalyzer.cs | 125 ++++++ .../src/ITypeSymbolExtensions.cs | 32 ++ .../src/Properties/AssemblyInfo.cs | 6 + ...xtensions.Http.AspNetCore.Analyzers.csproj | 2 +- .../ci/public-build.yml | 4 + .../release_notes.md | 6 +- .../ci/public-build.yml | 4 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 6 - .../src/Worker.Extensions.Kafka.csproj | 6 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 6 - .../src/Worker.Extensions.RabbitMQ.csproj | 4 + .../Worker.Extensions.Rpc/ci/public-build.yml | 4 + .../Worker.Extensions.Rpc/release_notes.md | 5 +- .../src/ConfigurationExtensions.cs | 11 + .../src/GrpcHttpClientBuilderExtensions.cs | 13 +- .../RpcServiceCollectionExtensions.NetApp.cs | 5 +- ...ServiceCollectionExtensions.NetStandard.cs | 16 +- .../src/Worker.Extensions.Rpc.csproj | 2 +- .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 6 - .../src/Worker.Extensions.SendGrid.csproj | 4 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 2 - .../src/Worker.Extensions.ServiceBus.csproj | 5 + .../ci/public-build.yml | 4 + .../release_notes.md | 4 +- .../src/Properties/AssemblyInfo.cs | 6 - .../Worker.Extensions.SignalRService.csproj | 6 +- .../ci/public-build.yml | 4 + .../src/BlobOutputAttribute.cs | 1 - .../src/Properties/AssemblyInfo.cs | 2 - .../Worker.Extensions.Storage.Blobs.csproj | 4 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 2 - .../Worker.Extensions.Storage.Queues.csproj | 4 + .../ci/public-build.yml | 4 + .../ci/public-build.yml | 4 + .../src/Properties/AssemblyInfo.cs | 2 - .../src/Worker.Extensions.Tables.csproj | 4 + .../ci/public-build.yml | 4 + .../ci/public-build.yml | 4 + global.json | 2 +- .../FunctionsNetHost/FunctionsNetHost.csproj | 2 +- host/src/FunctionsNetHost/global.json | 2 +- samples/AspNetIntegration/FileDownload.cs | 2 +- ...nctionMetadataProviderGenerator.Emitter.cs | 2 +- sdk/release_notes.md | 8 +- src/DotNetWorker.Core/ExceptionExtensions.cs | 31 ++ src/DotNetWorker.Core/FunctionsApplication.cs | 28 +- .../DefaultInputConverterInitializer.cs | 23 ++ .../Hosting/ServiceCollectionExtensions.cs | 36 +- .../Handlers/InvocationHandler.cs | 3 +- .../EndToEndTests.cs | 18 + .../ExceptionExtensionTests.cs | 36 ++ .../ServiceCollectionExtensionsTests.cs | 49 ++- test/E2ETests/E2EApps/E2EApp/E2EApp.csproj | 2 +- .../Sdk.Analyzers.Tests.csproj | 1 + .../FunctionExecutor/DependentAssemblyTest.cs | 20 +- .../AmbiguousNamespaceTests.cs | 146 +++++++ .../AutoConfigureStartupTypeTests.cs | 2 +- .../DependentAssemblyTest.NetFx.cs | 2 +- .../DependentAssemblyTest.cs | 4 +- .../EventHubsBindingsTests.cs | 12 +- .../HttpTriggerTests.cs | 8 +- .../IntegratedTriggersAndBindingsTests.cs | 16 +- .../KafkaTests.cs | 2 +- .../NestedTypesTest.cs | 4 +- .../RetryOptionsTests.cs | 4 +- .../ServiceBustTests.cs | 2 +- .../SignalRTest.cs | 2 +- .../StorageBindingTests.cs | 6 +- .../Sdk.Generator.Tests.csproj | 10 +- .../SymbolExtensionsTest.cs | 96 +++++ .../GrpcHttpClientBuilderExtensionsTests.cs | 24 +- .../HttpResultAttributeExpectedTests.cs | 365 ++++++++++++++++++ ...er.Extensions.Http.AspNetCore.Tests.csproj | 1 + 99 files changed, 1468 insertions(+), 153 deletions(-) create mode 100644 docs/analyzer-rules/AZFW0015.md create mode 100644 docs/analyzer-rules/AZFW0016.md create mode 100644 eng/build/WorkerExtensions.targets create mode 100644 eng/build/extensionValidationProjectTemplate.txt create mode 100644 extensions/Directory.Build.targets create mode 100644 extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/CodeFixForHttpResultAttributeExpected.cs create mode 100644 extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs create mode 100644 extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/ITypeSymbolExtensions.cs create mode 100644 extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Properties/AssemblyInfo.cs delete mode 100644 extensions/Worker.Extensions.Kafka/src/Properties/AssemblyInfo.cs delete mode 100644 extensions/Worker.Extensions.RabbitMQ/src/Properties/AssemblyInfo.cs delete mode 100644 extensions/Worker.Extensions.SendGrid/src/Properties/AssemblyInfo.cs delete mode 100644 extensions/Worker.Extensions.SignalRService/src/Properties/AssemblyInfo.cs create mode 100644 src/DotNetWorker.Core/ExceptionExtensions.cs create mode 100644 src/DotNetWorker.Core/Hosting/DefaultInputConverterInitializer.cs create mode 100644 test/DotNetWorkerTests/ExceptionExtensionTests.cs create mode 100644 test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs create mode 100644 test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs create mode 100644 test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs diff --git a/.gitignore b/.gitignore index 40203d35d..314074376 100644 --- a/.gitignore +++ b/.gitignore @@ -364,3 +364,4 @@ Migrations/ local.settings.json /tools/localpack.ps1 /.vscode +/.idea diff --git a/Directory.Build.props b/Directory.Build.props index 6fa3e578a..28a20401b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,4 +5,14 @@ true + + $(MSBuildThisFileDirectory) + $(RepoRoot)eng/ + $(EngRoot)build/ + + + + true + + diff --git a/docs/analyzer-rules/AZFW0014.md b/docs/analyzer-rules/AZFW0014.md index b9ec2f72a..8534e7dc6 100644 --- a/docs/analyzer-rules/AZFW0014.md +++ b/docs/analyzer-rules/AZFW0014.md @@ -1,4 +1,4 @@ -# AZFW0011: Missing Registration for ASP.NET Core Integration +# AZFW0014: Missing Registration for ASP.NET Core Integration | | Value | |-|-| diff --git a/docs/analyzer-rules/AZFW0015.md b/docs/analyzer-rules/AZFW0015.md new file mode 100644 index 000000000..59b04b17d --- /dev/null +++ b/docs/analyzer-rules/AZFW0015.md @@ -0,0 +1,44 @@ +# AZFW0015: Missing HttpResult attribute for multi-output function + +| | Value | +|-|-| +| **Rule ID** |AZFW00015| +| **Category** |[Usage]| +| **Severity** |Error| + +## Cause + +This rule is triggered when a multi-output function is missing a `HttpResultAttribute` on the HTTP response type. + +## Rule description + +For [functions with multiple output bindings](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide?tabs=windows#multiple-output-bindings) using ASP.NET Core integration, the property correlating with the HTTP response needs to be decorated with the `HttpResultAttribute` in order to write the HTTP response correctly. Properties of the type `HttpResponseData` will still have their responses written correctly. + +## How to fix violations + +Add the attribute `[HttpResult]` (or `[HttpResultAttribute]`) to the relevant property. Example: + +```csharp +public static class MultiOutput +{ + [Function(nameof(MultiOutput))] + public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req, + FunctionContext context) + { + ... + } +} + +public class MyOutputType +{ + [QueueOutput("myQueue")] + public string Name { get; set; } + + [HttpResult] + public IActionResult HttpResponse { get; set; } +} +``` + +## When to suppress warnings + +This rule should not be suppressed because this error will prevent the HTTP response from being written correctly. diff --git a/docs/analyzer-rules/AZFW0016.md b/docs/analyzer-rules/AZFW0016.md new file mode 100644 index 000000000..3ee171988 --- /dev/null +++ b/docs/analyzer-rules/AZFW0016.md @@ -0,0 +1,46 @@ +# AZFW0016: Missing HttpResult attribute for multi-output function + +| | Value | +|-|-| +| **Rule ID** |AZFW00016| +| **Category** |[Usage]| +| **Severity** |Warning| + +## Cause + +This rule is triggered when a multi-output function using `HttpResponseData` is missing a `HttpResultAttribute` on the HTTP response type. + +## Rule description + +Following the introduction of ASP.NET Core integration, for [functions with multiple output bindings](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide?tabs=windows#multiple-output-bindings), the property in a custom output type correlating with the HTTP response is expected to be decorated with the `HttpResultAttribute`. + +`HttpResponseData` does not require this attribute for multi-output functions to work because support for it was available before the introduction of ASP.NET Core Integration. However, this is the expected convention moving forward as all other HTTP response types in this scenario will not work without this attribute. + +## How to fix violations + +Add the attribute `[HttpResult]` (or `[HttpResultAttribute]`) to the relevant property. Example: + +```csharp +public static class MultiOutput +{ + [Function(nameof(MultiOutput))] + public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req, + FunctionContext context) + { + ... + } +} + +public class MyOutputType +{ + [QueueOutput("myQueue")] + public string Name { get; set; } + + [HttpResult] + public HttpResponseData HttpResponse { get; set; } +} +``` + +## When to suppress warnings + +This rule can be suppressed if there is no intention to migrate from `HttpResponseData` to other types (like `IActionResult`). \ No newline at end of file diff --git a/eng/build/WorkerExtensions.targets b/eng/build/WorkerExtensions.targets new file mode 100644 index 000000000..446ad9ca5 --- /dev/null +++ b/eng/build/WorkerExtensions.targets @@ -0,0 +1,30 @@ + + + <_ExtensionProjectTemplate>$(MSBuildThisFileDirectory)/extensionValidationProjectTemplate.txt + <_ExtensionValidationLocation>$(IntermediateOutputPath)ExtensionValidation/ + + + + + <_ExtensionInformationAttribute Include="@(WebJobsExtension->'Microsoft.Azure.Functions.Worker.Extensions.Abstractions.ExtensionInformationAttribute')"> + <_Parameter1>%(WebJobsExtension.Identity) + <_Parameter2>%(WebJobsExtension.Version) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/build/extensionValidationProjectTemplate.txt b/eng/build/extensionValidationProjectTemplate.txt new file mode 100644 index 000000000..c6e2d69dc --- /dev/null +++ b/eng/build/extensionValidationProjectTemplate.txt @@ -0,0 +1,10 @@ + + + net8.0 + Library + + + + + + \ No newline at end of file diff --git a/eng/ci/public-build.yml b/eng/ci/public-build.yml index e4a3bc602..169797320 100644 --- a/eng/ci/public-build.yml +++ b/eng/ci/public-build.yml @@ -55,6 +55,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/eng/ci/templates/jobs/run-unit-tests.yml b/eng/ci/templates/jobs/run-unit-tests.yml index 019da6934..241359e64 100644 --- a/eng/ci/templates/jobs/run-unit-tests.yml +++ b/eng/ci/templates/jobs/run-unit-tests.yml @@ -40,14 +40,6 @@ jobs: projects: | **\DotNetWorker.Opentelemetry.Tests.csproj - - task: DotNetCoreCLI@2 - displayName: Application Insights Tests - inputs: - command: test - arguments: -v n - projects: | - **\DotNetWorker.ApplicationInsights.Tests.csproj - - task: DotNetCoreCLI@2 displayName: Sdk Tests inputs: @@ -56,7 +48,7 @@ jobs: projects: | **\SdkTests.csproj **\Sdk.Analyzers.Tests.csproj - **\Sdk.Generatior.Tests.csproj + **\Sdk.Generator.Tests.csproj - task: DotNetCoreCLI@2 displayName: Extension Tests diff --git a/extensions/Directory.Build.targets b/extensions/Directory.Build.targets new file mode 100644 index 000000000..711472655 --- /dev/null +++ b/extensions/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml b/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml index 9eca48a6c..70c836505 100644 --- a/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml +++ b/extensions/Worker.Extensions.CosmosDB/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs index f4ab975d0..fa1c7b2fa 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Runtime.CompilerServices; -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.CosmosDB", "4.8.0")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj b/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj index 7eb148a9d..93bfdf713 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj +++ b/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj @@ -30,4 +30,8 @@ + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.EventGrid/ci/public-build.yml b/extensions/Worker.Extensions.EventGrid/ci/public-build.yml index 158c8a384..d95e3f1e2 100644 --- a/extensions/Worker.Extensions.EventGrid/ci/public-build.yml +++ b/extensions/Worker.Extensions.EventGrid/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.EventGrid/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.EventGrid/src/Properties/AssemblyInfo.cs index 863e07157..ef657b5c3 100644 --- a/extensions/Worker.Extensions.EventGrid/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.EventGrid/src/Properties/AssemblyInfo.cs @@ -4,5 +4,4 @@ using System.Runtime.CompilerServices; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.EventGrid", "3.4.2")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj b/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj index 172a7595e..35443895d 100644 --- a/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj +++ b/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj @@ -23,4 +23,8 @@ + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.EventHubs/ci/public-build.yml b/extensions/Worker.Extensions.EventHubs/ci/public-build.yml index 7694ef756..ef68d1e36 100644 --- a/extensions/Worker.Extensions.EventHubs/ci/public-build.yml +++ b/extensions/Worker.Extensions.EventHubs/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs index 50d300fa5..1cde8f92b 100644 --- a/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs @@ -2,7 +2,5 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Runtime.CompilerServices; -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.EventHubs", "6.3.5")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj b/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj index 483997042..48fc0247e 100644 --- a/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj +++ b/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj @@ -26,4 +26,8 @@ + + + + \ 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 new file mode 100644 index 000000000..df004ca11 --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/CodeFixForHttpResultAttributeExpected.cs @@ -0,0 +1,96 @@ +// 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.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodeFixForHttpResultAttribute)), Shared] + public sealed class CodeFixForHttpResultAttribute : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => + ImmutableArray.Create( + DiagnosticDescriptors.MultipleOutputHttpTriggerWithoutHttpResultAttribute.Id, + DiagnosticDescriptors.MultipleOutputWithHttpResponseDataWithoutHttpResultAttribute.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + Diagnostic diagnostic = context.Diagnostics.First(); + context.RegisterCodeFix(new AddHttpResultAttribute(context.Document, diagnostic), diagnostic); + + return Task.CompletedTask; + } + + /// + /// CodeAction implementation which adds the HttpResultAttribute on the return type of a function using the multi-output bindings pattern. + /// + private sealed class AddHttpResultAttribute : CodeAction + { + private readonly Document _document; + private readonly Diagnostic _diagnostic; + private const string ExpectedAttributeName = "HttpResult"; + + internal AddHttpResultAttribute(Document document, Diagnostic diagnostic) + { + this._document = document; + this._diagnostic = diagnostic; + } + + public override string Title => "Add HttpResultAttribute"; + + public override string EquivalenceKey => null; + + /// + /// Asynchronously retrieves the modified , with the HttpResultAttribute added to the relevant property. + /// + /// A token that can be used to propagate notifications that the operation should be canceled. + /// An updated object. + protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + // Get the syntax root of the document + var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var typeNode = root.FindNode(this._diagnostic.Location.SourceSpan) + .FirstAncestorOrSelf(); + + var typeSymbol = semanticModel.GetSymbolInfo(typeNode).Symbol; + var typeDeclarationSyntaxReference = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault(); + if (typeDeclarationSyntaxReference is null) + { + return _document; + } + + var typeDeclarationNode = await typeDeclarationSyntaxReference.GetSyntaxAsync(cancellationToken); + + var propertyNode = typeDeclarationNode.DescendantNodes() + .OfType() + .First(prop => + { + var propertyType = semanticModel.GetTypeInfo(prop.Type).Type; + return propertyType != null && (propertyType.Name == "IActionResult" || propertyType.Name == "HttpResponseData" || propertyType.Name == "IResult"); + }); + + var attribute = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName(ExpectedAttributeName)); + + var newPropertyNode = propertyNode + .AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(attribute))); + + var newRoot = root.ReplaceNode(propertyNode, newPropertyNode); + + return _document.WithSyntaxRoot(newRoot); + } + } + } +} diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/DiagnosticDescriptors.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/DiagnosticDescriptors.cs index a95118f5c..d3d9c1e12 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/DiagnosticDescriptors.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/DiagnosticDescriptors.cs @@ -18,5 +18,12 @@ private static DiagnosticDescriptor Create(string id, string title, string messa public static DiagnosticDescriptor CorrectRegistrationExpectedInAspNetIntegration { get; } = Create(id: "AZFW0014", title: "Missing expected registration of ASP.NET Core Integration services", messageFormat: "The registration for method '{0}' is expected for ASP.NET Core Integration.", category: Usage, severity: DiagnosticSeverity.Error); + public static DiagnosticDescriptor MultipleOutputHttpTriggerWithoutHttpResultAttribute { get; } + = Create(id: "AZFW0015", title: "Missing a HttpResultAttribute in multi-output function", messageFormat: "The return type for function '{0}' is missing a HttpResultAttribute on the HTTP response type property.", + category: Usage, severity: DiagnosticSeverity.Error); + + public static DiagnosticDescriptor MultipleOutputWithHttpResponseDataWithoutHttpResultAttribute { get; } + = Create(id: "AZFW0016", title: "Missing a HttpResultAttribute in multi-output function", messageFormat: "The return type for function '{0}' is missing a HttpResultAttribute on the HttpResponseData type property.", + category: Usage, severity: DiagnosticSeverity.Warning); } } diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs new file mode 100644 index 000000000..20484ddda --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/HttpResultAttributeExpectedAnalyzer.cs @@ -0,0 +1,125 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class HttpResultAttributeExpectedAnalyzer : DiagnosticAnalyzer + { + private const string FunctionAttributeFullName = "Microsoft.Azure.Functions.Worker.FunctionAttribute"; + private const string HttpTriggerAttributeFullName = "Microsoft.Azure.Functions.Worker.HttpTriggerAttribute"; + private const string HttpResultAttributeFullName = "Microsoft.Azure.Functions.Worker.HttpResultAttribute"; + public const string HttpResponseDataFullName = "Microsoft.Azure.Functions.Worker.Http.HttpResponseData"; + public const string OutputBindingFullName = "Microsoft.Azure.Functions.Worker.Extensions.Abstractions.OutputBindingAttribute"; + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.MultipleOutputHttpTriggerWithoutHttpResultAttribute, + DiagnosticDescriptors.MultipleOutputWithHttpResponseDataWithoutHttpResultAttribute); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); + context.RegisterSyntaxNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration); + } + + private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) + { + var semanticModel = context.SemanticModel; + var methodDeclaration = (MethodDeclarationSyntax)context.Node; + + var functionAttributeSymbol = semanticModel.Compilation.GetTypeByMetadataName(FunctionAttributeFullName); + var functionNameAttribute = methodDeclaration.AttributeLists + .SelectMany(attrList => attrList.Attributes) + .Where(attr => SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(attr).Type, functionAttributeSymbol)); + + if (!functionNameAttribute.Any()) + { + return; + } + + var functionName = functionNameAttribute.First().ArgumentList.Arguments[0]; // only one argument in FunctionAttribute which is the function name + + var httpTriggerAttributeSymbol = semanticModel.Compilation.GetTypeByMetadataName(HttpTriggerAttributeFullName); + var hasHttpTriggerAttribute = methodDeclaration.ParameterList.Parameters + .SelectMany(param => param.AttributeLists) + .SelectMany(attrList => attrList.Attributes) + .Select(attr => semanticModel.GetTypeInfo(attr).Type) + .Any(attrSymbol => SymbolEqualityComparer.Default.Equals(attrSymbol, httpTriggerAttributeSymbol)); + + if (!hasHttpTriggerAttribute) + { + return; + } + + var returnType = methodDeclaration.ReturnType; + var returnTypeSymbol = semanticModel.GetTypeInfo(returnType).Type; + + if (IsHttpReturnType(returnTypeSymbol, semanticModel)) + { + return; + } + + var outputBindingSymbol = semanticModel.Compilation.GetTypeByMetadataName(OutputBindingFullName); + var hasOutputBindingProperty = returnTypeSymbol.GetMembers() + .OfType() + .Any(prop => prop.GetAttributes().Any(attr => attr.AttributeClass.IsOrDerivedFrom(outputBindingSymbol))); + + if (!hasOutputBindingProperty) + { + return; + } + + var httpResponseDataSymbol = semanticModel.Compilation.GetTypeByMetadataName(HttpResponseDataFullName); + var hasHttpResponseData = returnTypeSymbol.GetMembers() + .OfType() + .Any(prop => SymbolEqualityComparer.Default.Equals(prop.Type, httpResponseDataSymbol)); + + var httpResultAttributeSymbol = semanticModel.Compilation.GetTypeByMetadataName(HttpResultAttributeFullName); + var hasHttpResultAttribute = returnTypeSymbol.GetMembers() + .SelectMany(member => member.GetAttributes()) + .Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, httpResultAttributeSymbol)); + + if (!hasHttpResultAttribute && !hasHttpResponseData) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.MultipleOutputHttpTriggerWithoutHttpResultAttribute, methodDeclaration.ReturnType.GetLocation(), functionName.ToString()); + context.ReportDiagnostic(diagnostic); + } + + if (!hasHttpResultAttribute && hasHttpResponseData) + { + var diagnostic = Diagnostic.Create(DiagnosticDescriptors.MultipleOutputWithHttpResponseDataWithoutHttpResultAttribute, methodDeclaration.ReturnType.GetLocation(), functionName.ToString()); + context.ReportDiagnostic(diagnostic); + } + + } + + private static bool IsHttpReturnType(ISymbol symbol, SemanticModel semanticModel) + { + var httpRequestDataType = semanticModel.Compilation.GetTypeByMetadataName("Microsoft.Azure.Functions.Worker.Http.HttpRequestData"); + + if (SymbolEqualityComparer.Default.Equals(symbol, httpRequestDataType)) + { + return true; + } + + var iActionResultType = semanticModel.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.IActionResult"); + var iResultType = semanticModel.Compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Http.IResult"); + + // these two types may be false if the user is not using ASP.NET Core Integration + if (SymbolEqualityComparer.Default.Equals(symbol, iActionResultType) || + SymbolEqualityComparer.Default.Equals(symbol, iResultType)) + { + return false; + } + + return SymbolEqualityComparer.Default.Equals(symbol, iActionResultType) || SymbolEqualityComparer.Default.Equals(symbol, iResultType); + } + } +} diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/ITypeSymbolExtensions.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/ITypeSymbolExtensions.cs new file mode 100644 index 000000000..25e7220f0 --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/ITypeSymbolExtensions.cs @@ -0,0 +1,32 @@ +// 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 static class ITypeSymbolExtensions + { + internal static bool IsOrDerivedFrom(this ITypeSymbol symbol, ITypeSymbol other) + { + if (other is null) + { + return false; + } + + var current = symbol; + + while (current != null) + { + if (SymbolEqualityComparer.Default.Equals(current, other) || SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, other)) + { + return true; + } + + current = current.BaseType; + } + + return false; + } + } +} diff --git a/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..bff3655d7 --- /dev/null +++ b/extensions/Worker.Extensions.Http.AspNetCore.Analyzers/src/Properties/AssemblyInfo.cs @@ -0,0 +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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] \ No newline at end of file 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 022514d9a..6d37a744e 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.2 + 1.0.3 Library true false diff --git a/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml b/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml index 3ee12526f..6e712e2aa 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml +++ b/extensions/Worker.Extensions.Http.AspNetCore/ci/public-build.yml @@ -52,6 +52,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md index 7b79522ed..ac26c8851 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md +++ b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md @@ -6,4 +6,8 @@ ### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore -- Fixed a bug that would lead to an empty exception message in some model binding failures. +- Updated`Updated Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Analyzers` 1.0.3 + +### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Analyzers 1.0.3 + +- Add analyzer that detects multiple-output binding scenarios for HTTP Trigger Functions. Read more about this scenario [here](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-output?tabs=isolated-process%2Cnodejs-v4&pivots=programming-language-csharp#usage) in our official docs. (#2706) diff --git a/extensions/Worker.Extensions.Http/ci/public-build.yml b/extensions/Worker.Extensions.Http/ci/public-build.yml index 2a80e02dc..58336e9f9 100644 --- a/extensions/Worker.Extensions.Http/ci/public-build.yml +++ b/extensions/Worker.Extensions.Http/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Kafka/ci/public-build.yml b/extensions/Worker.Extensions.Kafka/ci/public-build.yml index 462d2b6ef..3ddb3878c 100644 --- a/extensions/Worker.Extensions.Kafka/ci/public-build.yml +++ b/extensions/Worker.Extensions.Kafka/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Kafka/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Kafka/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 6b39cbf04..000000000 --- a/extensions/Worker.Extensions.Kafka/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +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.Azure.Functions.Worker.Extensions.Abstractions; - -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Kafka", "3.9.0")] diff --git a/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj b/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj index d72dba8fe..03ea9f9a1 100644 --- a/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj +++ b/extensions/Worker.Extensions.Kafka/src/Worker.Extensions.Kafka.csproj @@ -1,4 +1,5 @@  + Microsoft.Azure.Functions.Worker.Extensions.Kafka Microsoft.Azure.Functions.Worker.Extensions.Kafka @@ -18,4 +19,9 @@ + + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml b/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml index 464cb5a6f..7232c3074 100644 --- a/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml +++ b/extensions/Worker.Extensions.RabbitMQ/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.RabbitMQ/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.RabbitMQ/src/Properties/AssemblyInfo.cs deleted file mode 100644 index cd940dcb3..000000000 --- a/extensions/Worker.Extensions.RabbitMQ/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +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.Azure.Functions.Worker.Extensions.Abstractions; - -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.RabbitMQ", "2.0.3")] diff --git a/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj b/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj index 5984c9f17..0868de5ac 100644 --- a/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj +++ b/extensions/Worker.Extensions.RabbitMQ/src/Worker.Extensions.RabbitMQ.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/extensions/Worker.Extensions.Rpc/ci/public-build.yml b/extensions/Worker.Extensions.Rpc/ci/public-build.yml index 7f660a636..84d671352 100644 --- a/extensions/Worker.Extensions.Rpc/ci/public-build.yml +++ b/extensions/Worker.Extensions.Rpc/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Rpc/release_notes.md b/extensions/Worker.Extensions.Rpc/release_notes.md index ddca44e2c..45f46f3e9 100644 --- a/extensions/Worker.Extensions.Rpc/release_notes.md +++ b/extensions/Worker.Extensions.Rpc/release_notes.md @@ -4,7 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.0 +### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.1 -- Initial public release -- Adds API for getting a `CallInvoker` pre-configured for communication with Functions host. \ No newline at end of file +- Set max message send and receive length on gRPC `CallInvoker`. diff --git a/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs b/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs index cb2bc2132..64e4ff8fc 100644 --- a/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs +++ b/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs @@ -38,5 +38,16 @@ public static Uri GetFunctionsHostGrpcUri(this IConfiguration configuration) return grpcUri; } + + /// + /// Gets the maximum message length for the functions host gRPC channel. + /// + /// The configuration to retrieve values from. + /// The maximum message length if available. + public static int? GetFunctionsHostMaxMessageLength(this IConfiguration configuration) + { + return configuration.GetValue("Functions:Worker:GrpcMaxMessageLength", null) + ?? configuration.GetValue("grpcMaxMessageLength", null); + } } } diff --git a/extensions/Worker.Extensions.Rpc/src/GrpcHttpClientBuilderExtensions.cs b/extensions/Worker.Extensions.Rpc/src/GrpcHttpClientBuilderExtensions.cs index c4dd5cf44..b659baad2 100644 --- a/extensions/Worker.Extensions.Rpc/src/GrpcHttpClientBuilderExtensions.cs +++ b/extensions/Worker.Extensions.Rpc/src/GrpcHttpClientBuilderExtensions.cs @@ -31,7 +31,18 @@ public static IHttpClientBuilder ConfigureForFunctionsHostGrpc(this IHttpClientB ValidateGrpcClient(builder); builder.Services.AddOptions(builder.Name) - .Configure((options, config) => options.Address = config.GetFunctionsHostGrpcUri()); + .Configure((options, config) => + { + options.Address = config.GetFunctionsHostGrpcUri(); + if (config.GetFunctionsHostMaxMessageLength() is int length) + { + options.ChannelOptionsActions.Add(o => + { + o.MaxReceiveMessageSize = length; + o.MaxSendMessageSize = length; + }); + } + }); return builder; } diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs index ff0780c38..ac42ac40a 100644 --- a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.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. #if !NETSTANDARD @@ -19,8 +19,7 @@ private static void ConfigureCallInvoker(IServiceCollection services) { // Instead of building the GrpcChannel/CallInvoker ourselves, we use Grpc.Net.ClientFactory to // construct and configure the CallInvoker for us, then we attach that to our options. - services.AddGrpcClient(_ => { }) - .ConfigureForFunctionsHostGrpc(); + services.AddGrpcClient(_ => { }).ConfigureForFunctionsHostGrpc(); services.TryAddEnumerable( ServiceDescriptor.Transient, ConfigureOptions>()); } diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs index 743473bc2..fd122a1c4 100644 --- a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.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. #if NETSTANDARD using System; +using System.Collections.Generic; +using System.Linq; using Grpc.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -33,8 +35,18 @@ public ConfigureOptions(IConfiguration configuration) public void Configure(FunctionsGrpcOptions options) { + IEnumerable channelOptions = _configuration.GetFunctionsHostMaxMessageLength() switch + { + int maxMessageLength => new[] + { + new ChannelOption(ChannelOptions.MaxReceiveMessageLength, maxMessageLength), + new ChannelOption(ChannelOptions.MaxSendMessageLength, maxMessageLength), + }, + _ => Enumerable.Empty(), + }; + Uri address = _configuration.GetFunctionsHostGrpcUri(); - Channel c = new Channel(address.Host, address.Port, ChannelCredentials.Insecure); + Channel c = new Channel(address.Host, address.Port, ChannelCredentials.Insecure, channelOptions); options.CallInvoker = c.CreateCallInvoker(); } } diff --git a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj index 3ff8a7ce9..b9aa4156f 100644 --- a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj +++ b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj @@ -5,7 +5,7 @@ Microsoft.Azure.Functions.Worker.Extensions.Rpc Microsoft.Azure.Functions.Worker.Extensions.Rpc Contains types to facilitate RPC communication between a worker extension and the functions host. - 1.0.0 + 1.0.1 README.md diff --git a/extensions/Worker.Extensions.SendGrid/ci/public-build.yml b/extensions/Worker.Extensions.SendGrid/ci/public-build.yml index e66be115d..b47181cdb 100644 --- a/extensions/Worker.Extensions.SendGrid/ci/public-build.yml +++ b/extensions/Worker.Extensions.SendGrid/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.SendGrid/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.SendGrid/src/Properties/AssemblyInfo.cs deleted file mode 100644 index f99ccf250..000000000 --- a/extensions/Worker.Extensions.SendGrid/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +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.Azure.Functions.Worker.Extensions.Abstractions; - -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.SendGrid", "3.0.3")] diff --git a/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj b/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj index d3160ec6f..44e1f3221 100644 --- a/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj +++ b/extensions/Worker.Extensions.SendGrid/src/Worker.Extensions.SendGrid.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml b/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml index 4c97838e2..3da6a0ad4 100644 --- a/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml +++ b/extensions/Worker.Extensions.ServiceBus/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs index c192d57e4..1cde8f92b 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs @@ -2,7 +2,5 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Runtime.CompilerServices; -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.16.4")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] diff --git a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj index 4e0192263..38d7c0075 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -35,4 +35,9 @@ + + + + + diff --git a/extensions/Worker.Extensions.SignalRService/ci/public-build.yml b/extensions/Worker.Extensions.SignalRService/ci/public-build.yml index eee535950..50784ea6d 100644 --- a/extensions/Worker.Extensions.SignalRService/ci/public-build.yml +++ b/extensions/Worker.Extensions.SignalRService/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.SignalRService/release_notes.md b/extensions/Worker.Extensions.SignalRService/release_notes.md index 68259f080..1f4444583 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.14.1 +### Microsoft.Azure.Functions.Worker.Extensions.SignalRService 1.15.0 -- Updated `Microsoft.Extensions.Azure` to 1.7.5 +- Fix SignalR trigger return value not working issue. diff --git a/extensions/Worker.Extensions.SignalRService/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.SignalRService/src/Properties/AssemblyInfo.cs deleted file mode 100644 index a66521d25..000000000 --- a/extensions/Worker.Extensions.SignalRService/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +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.Azure.Functions.Worker.Extensions.Abstractions; - -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.SignalRService", "1.14.0")] diff --git a/extensions/Worker.Extensions.SignalRService/src/Worker.Extensions.SignalRService.csproj b/extensions/Worker.Extensions.SignalRService/src/Worker.Extensions.SignalRService.csproj index a8744bbae..925fdb45f 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.14.1 + 1.15.0 false @@ -26,4 +26,8 @@ + + + + diff --git a/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml b/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml index d145379a8..5332122d4 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml +++ b/extensions/Worker.Extensions.Storage.Blobs/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/BlobOutputAttribute.cs b/extensions/Worker.Extensions.Storage.Blobs/src/BlobOutputAttribute.cs index e448dc239..e51e82cc9 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/BlobOutputAttribute.cs +++ b/extensions/Worker.Extensions.Storage.Blobs/src/BlobOutputAttribute.cs @@ -11,7 +11,6 @@ public sealed class BlobOutputAttribute : OutputBindingAttribute private readonly string _blobPath; /// Initializes a new instance of the class. - /// The name of the property to which to bind /// The path of the blob to which to bind. public BlobOutputAttribute(string blobPath) { diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs index e84e64ac5..2a67c8eb2 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Runtime.CompilerServices; -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs", "5.3.1")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] 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 db94a7f64..cd0eb9504 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 @@ -29,4 +29,8 @@ + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml b/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml index 240ce2d02..9d1b5adac 100644 --- a/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml +++ b/extensions/Worker.Extensions.Storage.Queues/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Storage.Queues/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Storage.Queues/src/Properties/AssemblyInfo.cs index 117516432..1cde8f92b 100644 --- a/extensions/Worker.Extensions.Storage.Queues/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.Storage.Queues/src/Properties/AssemblyInfo.cs @@ -2,7 +2,5 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Runtime.CompilerServices; -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Storage.Queues", "5.3.1")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] 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 a22a3b6cb..e70a7b744 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 @@ -27,4 +27,8 @@ + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Storage/ci/public-build.yml b/extensions/Worker.Extensions.Storage/ci/public-build.yml index fa7094d3b..ca4c1c76b 100644 --- a/extensions/Worker.Extensions.Storage/ci/public-build.yml +++ b/extensions/Worker.Extensions.Storage/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Tables/ci/public-build.yml b/extensions/Worker.Extensions.Tables/ci/public-build.yml index ca1df0174..624f5998a 100644 --- a/extensions/Worker.Extensions.Tables/ci/public-build.yml +++ b/extensions/Worker.Extensions.Tables/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Tables/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Tables/src/Properties/AssemblyInfo.cs index 4726d32e8..fa1c7b2fa 100644 --- a/extensions/Worker.Extensions.Tables/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.Tables/src/Properties/AssemblyInfo.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Runtime.CompilerServices; -using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Tables", "1.3.2")] [assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj b/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj index 6f2046012..ec390ab98 100644 --- a/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj +++ b/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj @@ -27,5 +27,9 @@ + + + + \ No newline at end of file diff --git a/extensions/Worker.Extensions.Timer/ci/public-build.yml b/extensions/Worker.Extensions.Timer/ci/public-build.yml index cbe766e59..b4176c73b 100644 --- a/extensions/Worker.Extensions.Timer/ci/public-build.yml +++ b/extensions/Worker.Extensions.Timer/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/extensions/Worker.Extensions.Warmup/ci/public-build.yml b/extensions/Worker.Extensions.Warmup/ci/public-build.yml index bf72c8ae0..8d0db8be9 100644 --- a/extensions/Worker.Extensions.Warmup/ci/public-build.yml +++ b/extensions/Worker.Extensions.Warmup/ci/public-build.yml @@ -50,6 +50,10 @@ extends: image: 1es-windows-2022 os: windows + settings: + # PR's from forks do not have sufficient permissions to set tags. + skipBuildTagsForGitHubPullRequests: ${{ variables['System.PullRequest.IsFork'] }} + stages: - stage: Test diff --git a/global.json b/global.json index 4ac08fdaf..b1a787224 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "8.0.110", "rollForward": "latestFeature" }, "msbuild-sdks": { diff --git a/host/src/FunctionsNetHost/FunctionsNetHost.csproj b/host/src/FunctionsNetHost/FunctionsNetHost.csproj index 776b0ffd7..9b9495015 100644 --- a/host/src/FunctionsNetHost/FunctionsNetHost.csproj +++ b/host/src/FunctionsNetHost/FunctionsNetHost.csproj @@ -28,7 +28,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/host/src/FunctionsNetHost/global.json b/host/src/FunctionsNetHost/global.json index 989a69caf..26228fbdf 100644 --- a/host/src/FunctionsNetHost/global.json +++ b/host/src/FunctionsNetHost/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "8.0.110", "rollForward": "latestMinor" } } \ No newline at end of file diff --git a/samples/AspNetIntegration/FileDownload.cs b/samples/AspNetIntegration/FileDownload.cs index 1df18eb6e..aac49c0c1 100644 --- a/samples/AspNetIntegration/FileDownload.cs +++ b/samples/AspNetIntegration/FileDownload.cs @@ -10,7 +10,7 @@ public class FileDownload private const string BlobContainer = "runtimes"; // Replace this with your blob name - private const string BlobName = "dotnet-sdk-8.0.100-win-x64.exe"; + private const string BlobName = "dotnet-sdk-8.0.110-win-x64.exe"; [Function("FileDownload")] public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs index a715eeacf..0fa52199e 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs @@ -49,7 +49,7 @@ public Task> GetFunctionMetadataAsync(string d { var metadataList = new List(); {{functionMetadataInfo}} - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/sdk/release_notes.md b/sdk/release_notes.md index ec5ffdba2..67afe6f17 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -4,12 +4,10 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Sdk 1.18.1 +### Microsoft.Azure.Functions.Worker.Sdk -- Updated `Microsoft.Azure.Functions.Worker.Sdk.Generators` reference to 1.3.4. +- Changed exception handling in function invocation path to ensure fatal exceptions bubble up. -### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.3.4 - -- Changed `FunctionExecutorGenerator` to avoid generation of long `if`/`else` chains for apps with a large number of functions. +### Microsoft.Azure.Functions.Worker.Sdk.Generators - diff --git a/src/DotNetWorker.Core/ExceptionExtensions.cs b/src/DotNetWorker.Core/ExceptionExtensions.cs new file mode 100644 index 000000000..5080d4f7a --- /dev/null +++ b/src/DotNetWorker.Core/ExceptionExtensions.cs @@ -0,0 +1,31 @@ +// 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.Core +{ + internal static class ExceptionExtensions + { + public static bool IsFatal(this Exception? exception) + { + while (exception is not null) + { + if (exception + is (OutOfMemoryException and not InsufficientMemoryException) + or AppDomainUnloadedException + or BadImageFormatException + or CannotUnloadAppDomainException + or InvalidProgramException + or AccessViolationException) + { + return true; + } + + exception = exception.InnerException; + } + + return false; + } + } +} diff --git a/src/DotNetWorker.Core/FunctionsApplication.cs b/src/DotNetWorker.Core/FunctionsApplication.cs index e718d9e04..11db9d003 100644 --- a/src/DotNetWorker.Core/FunctionsApplication.cs +++ b/src/DotNetWorker.Core/FunctionsApplication.cs @@ -67,18 +67,23 @@ public void LoadFunction(FunctionDefinition definition) public async Task InvokeFunctionAsync(FunctionContext context) { - // This will act as an internal activity that represents remote Host activity. This cannot be tracked as this is not associate to an ActivitySource. - using Activity activity = new Activity(nameof(InvokeFunctionAsync)); - activity.Start(); + Activity? activity = null; - if (ActivityContext.TryParse(context.TraceContext.TraceParent, context.TraceContext.TraceState, true, out ActivityContext activityContext)) + if (Activity.Current is null) { - 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; + // 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); @@ -98,6 +103,9 @@ public async Task InvokeFunctionAsync(FunctionContext context) throw; } + + invokeActivity?.Stop(); + activity?.Stop(); } } } diff --git a/src/DotNetWorker.Core/Hosting/DefaultInputConverterInitializer.cs b/src/DotNetWorker.Core/Hosting/DefaultInputConverterInitializer.cs new file mode 100644 index 000000000..67f207663 --- /dev/null +++ b/src/DotNetWorker.Core/Hosting/DefaultInputConverterInitializer.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.Azure.Functions.Worker.Converters; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker.Core; + +internal class DefaultInputConverterInitializer : IConfigureOptions +{ + public void Configure(WorkerOptions options) + { + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + options.InputConverters.Register(); + } +} diff --git a/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs b/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs index b323d4a7d..65481e3ed 100644 --- a/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs +++ b/src/DotNetWorker.Core/Hosting/ServiceCollectionExtensions.cs @@ -81,7 +81,7 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerCore(this ISe } }); - services.AddSingleton(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.AddSingleton(NullLogWriter.Instance); services.AddSingleton(s => s.GetRequiredService()); services.AddSingleton(s => s.GetRequiredService()); @@ -93,7 +93,25 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerCore(this ISe services.Configure(configure); } - var builder = new FunctionsWorkerApplicationBuilder(services); + IFunctionsWorkerApplicationBuilder builder = null!; + + // We want to ensure that if this is called multiple times, we use the same builder, + // so we stash in the IServiceCollection for future calls to check. + foreach (var descriptor in services) + { + if (descriptor.ServiceType == typeof(IFunctionsWorkerApplicationBuilder)) + { + builder = (IFunctionsWorkerApplicationBuilder)descriptor.ImplementationInstance!; + break; + } + } + + if (builder is null) + { + builder = new FunctionsWorkerApplicationBuilder(services); + services.AddSingleton(builder); + } + // Execute startup code from worker extensions if present. RunExtensionStartupCode(builder); @@ -105,18 +123,8 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorkerCore(this ISe /// internal static IServiceCollection AddDefaultInputConvertersToWorkerOptions(this IServiceCollection services) { - return services.Configure((workerOption) => - { - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - workerOption.InputConverters.Register(); - }); + services.TryAddEnumerable(ServiceDescriptor.Singleton, DefaultInputConverterInitializer>()); + return services; } /// diff --git a/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs b/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs index 52688ea7a..8ddfaea64 100644 --- a/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs +++ b/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Context.Features; +using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Azure.Functions.Worker.Grpc; using Microsoft.Azure.Functions.Worker.Grpc.Features; using Microsoft.Azure.Functions.Worker.Grpc.Messages; @@ -113,7 +114,7 @@ public async Task InvokeAsync(InvocationRequest request) response.Result.Status = StatusResult.Types.Status.Success; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { response.Result.Exception = _workerOptions.EnableUserCodeException ? ex.ToUserRpcException() : ex.ToRpcException(); response.Result.Status = StatusResult.Types.Status.Failure; diff --git a/test/DotNetWorker.OpenTelemetry.Tests/EndToEndTests.cs b/test/DotNetWorker.OpenTelemetry.Tests/EndToEndTests.cs index 2deaf671e..7ed21080c 100644 --- a/test/DotNetWorker.OpenTelemetry.Tests/EndToEndTests.cs +++ b/test/DotNetWorker.OpenTelemetry.Tests/EndToEndTests.cs @@ -91,6 +91,24 @@ public async Task ContextPropagation() } } + [Fact] + public async Task ContextPropagationWithTriggerInstrumentation() + { + using var host = InitializeHost(); + var context = CreateContext(host); + using Activity testActivity = new Activity("ASPNetCoreMockActivity"); + testActivity.Start(); + await _application.InvokeFunctionAsync(context); + var activity = OtelFunctionDefinition.LastActivity; + + Assert.Equal(activity.Id, testActivity.Id); + Assert.Equal(activity.OperationName, testActivity.OperationName); + Assert.Equal(activity.SpanId, testActivity.SpanId); + Assert.Equal(activity.TraceId, testActivity.TraceId); + Assert.Equal(activity.ActivityTraceFlags, testActivity.ActivityTraceFlags); + Assert.Equal(activity.TraceStateString, testActivity.TraceStateString); + } + [Fact] public void ResourceDetectorLocalDevelopment() { diff --git a/test/DotNetWorkerTests/ExceptionExtensionTests.cs b/test/DotNetWorkerTests/ExceptionExtensionTests.cs new file mode 100644 index 000000000..edf020c44 --- /dev/null +++ b/test/DotNetWorkerTests/ExceptionExtensionTests.cs @@ -0,0 +1,36 @@ +// 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; +using Xunit; + +namespace Microsoft.Azure.Functions.Worker.Tests +{ + public class ExceptionExtensionTests + { + [Theory] + [ClassData(typeof(ExceptionTestData))] + public void IsFatal_ReturnsTrueForFatalExceptions(Exception exception, bool isFatal) + { + var result = exception.IsFatal(); + + Assert.Equal(result, isFatal); + } + + public class ExceptionTestData : TheoryData + { + public ExceptionTestData() + { + Add(new OutOfMemoryException(), true); + Add(new AppDomainUnloadedException(), true); + Add(new BadImageFormatException(), true); + Add(new CannotUnloadAppDomainException(), true); + Add(new InvalidProgramException(), true); + Add(new AccessViolationException(), true); + Add(new InsufficientMemoryException(), false); + Add(new Exception("test", new OutOfMemoryException()), true); + } + } + } +} diff --git a/test/DotNetWorkerTests/ServiceCollectionExtensionsTests.cs b/test/DotNetWorkerTests/ServiceCollectionExtensionsTests.cs index 4a40be0bf..a2bd75fb9 100644 --- a/test/DotNetWorkerTests/ServiceCollectionExtensionsTests.cs +++ b/test/DotNetWorkerTests/ServiceCollectionExtensionsTests.cs @@ -1,7 +1,10 @@ // 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.DependencyInjection; +using System.Linq; +using Microsoft.Azure.Functions.Worker.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; @@ -26,5 +29,49 @@ public void ConfigureOptions_IsCalled() Assert.True(configured); } + + [Fact] + public void DefaultInputConverters_RegisteredOnce() + { + var serviceColl = new ServiceCollection(); + serviceColl.AddFunctionsWorkerDefaults(); + serviceColl.AddFunctionsWorkerDefaults(); + + var services = serviceColl.BuildServiceProvider(); + + // request the worker options, which forces their configuration to be called. + var workerOptions = services.GetService>().Value; + + // Ensure that even though we've called the registration twice, only one + // set of default input converters is registered. + var count = workerOptions.InputConverters.Count(); + Assert.Equal(9, count); + } + + [Fact] + public void LoggerProvider_RegisteredOnce() + { + var serviceColl = new ServiceCollection(); + serviceColl.AddFunctionsWorkerDefaults(); + serviceColl.AddFunctionsWorkerDefaults(); + + var services = serviceColl.BuildServiceProvider(); + + var loggerProviders = services.GetServices(); + + // Ensure that even though we've called the registration twice, only one + // WorkerLoggerProvider is registered. + Assert.Single(loggerProviders.Where(p => p is WorkerLoggerProvider)); + } + + [Fact] + public void SameBuilder_Returned() + { + var serviceColl = new ServiceCollection(); + var builder1 = serviceColl.AddFunctionsWorkerCore(); + var builder2 = serviceColl.AddFunctionsWorkerCore(); + + Assert.Same(builder1, builder2); + } } } diff --git a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj index 456a4ec51..7269347c3 100644 --- a/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj +++ b/test/E2ETests/E2EApps/E2EApp/E2EApp.csproj @@ -48,6 +48,6 @@ - + \ No newline at end of file diff --git a/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj b/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj index b84dc015b..d0a849c7a 100644 --- a/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj +++ b/test/Sdk.Analyzers.Tests/Sdk.Analyzers.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs index 7d60c7e3c..6a446b739 100644 --- a/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs +++ b/test/Sdk.Generator.Tests/FunctionExecutor/DependentAssemblyTest.cs @@ -101,47 +101,39 @@ public DirectFunctionExecutor(global::Microsoft.Azure.Functions.Worker.IFunction var instanceType = types["MyCompany.MyHttpTriggers"]; var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyHttpTriggers; context.GetInvocationResult().Value = i.Foo((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]); - goto end; + return; } - if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.DependencyFunction.Run", StringComparison.Ordinal)) { var instanceType = types["DependentAssemblyWithFunctions.DependencyFunction"]; var i = _functionActivator.CreateInstance(instanceType, context) as global::DependentAssemblyWithFunctions.DependencyFunction; context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]); - goto end; + return; } - if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.InternalFunction.Run", StringComparison.Ordinal)) { await _defaultExecutor.Value.ExecuteAsync(context); - goto end; + return; } - if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.StaticFunction.Run", StringComparison.Ordinal)) { context.GetInvocationResult().Value = global::DependentAssemblyWithFunctions.StaticFunction.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]); - goto end; + return; } - if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyProduct.MyApp.HttpFunctions.Run", StringComparison.Ordinal)) { var instanceType = types["MyCompany.MyProduct.MyApp.HttpFunctions"]; var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyProduct.MyApp.HttpFunctions; context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]); - goto end; + return; } - if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyProduct.MyApp.Foo.Bar.Run", StringComparison.Ordinal)) { var instanceType = types["MyCompany.MyProduct.MyApp.Foo.Bar"]; var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyProduct.MyApp.Foo.Bar; context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]); - goto end; + return; } - - end: - return; } private global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor CreateDefaultExecutorInstance(global::Microsoft.Azure.Functions.Worker.FunctionContext context) diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs new file mode 100644 index 000000000..207ae5e06 --- /dev/null +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AmbiguousNamespaceTests.cs @@ -0,0 +1,146 @@ +// 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.Worker.Sdk.Generators; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.Azure.Functions.SdkGeneratorTests +{ + public partial class FunctionMetadataProviderGeneratorTests + { + public class AmbiguousNamespaceTests + { + private readonly Assembly[] _referencedExtensionAssemblies; + + public AmbiguousNamespaceTests() + { + // 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 hostingExtension = typeof(HostBuilder).Assembly; + var diExtension = typeof(DefaultServiceProviderFactory).Assembly; + var hostingAbExtension = typeof(IHost).Assembly; + var diAbExtension = typeof(IServiceCollection).Assembly; + + _referencedExtensionAssemblies = new[] + { + abstractionsExtension, + httpExtension, + hostingExtension, + hostingAbExtension, + diExtension, + diAbExtension + }; + } + + [Theory] + [InlineData(LanguageVersion.CSharp7_3)] + [InlineData(LanguageVersion.CSharp8)] + [InlineData(LanguageVersion.CSharp9)] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + [InlineData(LanguageVersion.Latest)] + public async Task NamespaceEndingWithTask(LanguageVersion languageVersion) + { + string inputCode = """ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Http; + + namespace MyCompany.Task + { + public static class HttpTriggerSimple + { + [Function(nameof(HttpTriggerSimple))] + public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.User, "get")] HttpRequestData req, FunctionContext c) + { + return Run(req); + } + + public static HttpResponseData Run(HttpRequestData req) + => req.CreateResponse(System.Net.HttpStatusCode.OK); + } + } + """; + + 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)] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + 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"":""User"",""methods"":[""get""]}"); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function0 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "HttpTriggerSimple", + EntryPoint = "MyCompany.Task.HttpTriggerSimple.Run", + 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. + /// + 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/AutoConfigureStartupTypeTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AutoConfigureStartupTypeTests.cs index ae6dbdc56..3ca59c1bf 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AutoConfigureStartupTypeTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/AutoConfigureStartupTypeTests.cs @@ -112,7 +112,7 @@ public Task> GetFunctionMetadataAsync(string d }}; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); }} }} diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs index 9b2e87c00..007b680b7 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.NetFx.cs @@ -135,7 +135,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function2); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs index 7de6e6864..1890d6dd0 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs @@ -166,7 +166,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function5); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -307,7 +307,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function4); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs index 8c4264741..929886732 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs @@ -124,7 +124,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -258,7 +258,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -361,7 +361,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -552,7 +552,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function3); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -693,7 +693,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function1); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -821,7 +821,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function1); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs index b95db3252..8cb0b632b 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/HttpTriggerTests.cs @@ -106,7 +106,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -208,7 +208,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -315,7 +315,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -442,7 +442,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function1); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs index 44ffaab66..115384f84 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs @@ -158,7 +158,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function1); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -299,7 +299,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function1); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -423,7 +423,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -532,7 +532,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -627,7 +627,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -723,7 +723,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -854,7 +854,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function2); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -987,7 +987,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function2); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs index fa0052b08..48e0c3819 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/KafkaTests.cs @@ -114,7 +114,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs index adbcbff6a..a37b6afb7 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/NestedTypesTest.cs @@ -112,7 +112,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -216,7 +216,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs index 7800fe95e..0046b717b 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/RetryOptionsTests.cs @@ -117,7 +117,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -224,7 +224,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs index d08eb4aec..c4f7d3c61 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/ServiceBustTests.cs @@ -115,7 +115,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs index e136a42aa..e11cb3d83 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/SignalRTest.cs @@ -106,7 +106,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs index b632d0c2d..2df2f73cb 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/StorageBindingTests.cs @@ -114,7 +114,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -262,7 +262,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function2); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } @@ -367,7 +367,7 @@ public Task> GetFunctionMetadataAsync(string d }; metadataList.Add(Function0); - return Task.FromResult(metadataList.ToImmutableArray()); + return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray()); } } diff --git a/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj b/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj index e30977b68..f48af3b2e 100644 --- a/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj +++ b/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj @@ -19,10 +19,10 @@ - - - - + + + + @@ -34,7 +34,9 @@ all + + diff --git a/test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs b/test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs new file mode 100644 index 000000000..0eb389a99 --- /dev/null +++ b/test/Sdk.Generator.Tests/SymbolExtensionsTest/SymbolExtensionsTest.cs @@ -0,0 +1,96 @@ +// 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 Microsoft.Azure.Functions.Worker.Sdk.Generators; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; + +namespace Microsoft.Azure.Functions.SdkGeneratorTests.SymbolExtensionsTest +{ + public class SymbolExtensionsTest + { + [Fact] + public void TestIsOrDerivedFrom_WhenImplementationExists() + { + var sourceCode = @" + internal class BaseAttribute + { + } + + internal class FooAttribute : BaseAttribute + { + } + + internal class FooAttributeTwo : FooAttribute + { + }"; + + var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode); + var compilation = CSharpCompilation.Create("MyCompilation", new[] { syntaxTree }); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + // Retrieve the symbol for the FooOutAttribute class + var root = syntaxTree.GetRoot(); + var baseAttributeClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "BaseAttribute"); + var fooClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "FooAttribute"); + var fooTwoClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "FooAttributeTwo"); + var baseAttributeSymbol = semanticModel.GetDeclaredSymbol(baseAttributeClassDeclaration); + var fooSymbol = semanticModel.GetDeclaredSymbol(fooClassDeclaration); + var fooTwoSymbol = semanticModel.GetDeclaredSymbol(fooTwoClassDeclaration); + + Assert.NotNull(baseAttributeSymbol); + Assert.NotNull(fooSymbol); + Assert.NotNull(fooTwoSymbol); + + Assert.True(fooSymbol.IsOrDerivedFrom(baseAttributeSymbol)); + Assert.True(fooTwoSymbol.IsOrDerivedFrom(baseAttributeSymbol)); + Assert.True(fooTwoSymbol.IsOrDerivedFrom(fooSymbol)); + } + + [Fact] + public void TestIsOrDerivedFrom_WhenImplementationDoesNotExist() + { + var sourceCode = @" + internal class BaseAttribute + { + } + + internal class FooAttribute + { + } + + internal class OtherBaseAttribute + { + } + + internal class OtherFooAttribute : OtherBaseAttribute + { + }"; + + var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode); + var compilation = CSharpCompilation.Create("MyCompilation", new[] { syntaxTree }); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + // Retrieve the symbol for the FooOutAttribute class + var root = syntaxTree.GetRoot(); + var baseAttributeClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "BaseAttribute"); + var fooClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "FooAttribute"); + var otherBaseAttributeClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "OtherBaseAttribute"); + var otherFooClassDeclaration = root.DescendantNodes().OfType().First(cd => cd.Identifier.Text == "OtherFooAttribute"); + var baseAttributeSymbol = semanticModel.GetDeclaredSymbol(baseAttributeClassDeclaration); + var fooSymbol = semanticModel.GetDeclaredSymbol(fooClassDeclaration); + var otherBaseAttributeSymbol = semanticModel.GetDeclaredSymbol(otherBaseAttributeClassDeclaration); + var otherFooSymbol = semanticModel.GetDeclaredSymbol(otherFooClassDeclaration); + + Assert.NotNull(baseAttributeSymbol); + Assert.NotNull(fooSymbol); + Assert.NotNull(otherBaseAttributeSymbol); + Assert.NotNull(otherFooSymbol); + + Assert.False(fooSymbol.IsOrDerivedFrom(baseAttributeSymbol)); + Assert.False(otherFooSymbol.IsOrDerivedFrom(baseAttributeSymbol)); + } + } +} diff --git a/test/Worker.Extensions.Rpc.Tests/GrpcHttpClientBuilderExtensionsTests.cs b/test/Worker.Extensions.Rpc.Tests/GrpcHttpClientBuilderExtensionsTests.cs index eeb1475d7..e1eb9ffe6 100644 --- a/test/Worker.Extensions.Rpc.Tests/GrpcHttpClientBuilderExtensionsTests.cs +++ b/test/Worker.Extensions.Rpc.Tests/GrpcHttpClientBuilderExtensionsTests.cs @@ -4,6 +4,7 @@ #if NET6_0_OR_GREATER using Grpc.Core; +using Grpc.Net.Client; using Grpc.Net.ClientFactory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -23,7 +24,11 @@ public void ConfigureForFunctionsHostGrpc_Configure_SetsUri() public void ConfigureForFunctionsHostGrpc_SetsUri() => ConfigureForFunctionsHostGrpc(s => s.AddGrpcClient()); - private void ConfigureForFunctionsHostGrpc(Func configure) + [Fact] + public void ConfigureForFunctionsHostGrpc_SetsMessageSize() + => ConfigureForFunctionsHostGrpc(s => s.AddGrpcClient(), Random.Shared.Next(4098, 10000)); + + private void ConfigureForFunctionsHostGrpc(Func configure, int? maxMessageLength = null) { int port = 21584; // random enough. ConfigurationBuilder configBuilder = new(); @@ -31,6 +36,7 @@ private void ConfigureForFunctionsHostGrpc(Func monitor = sp.GetService>(); - GrpcClientFactoryOptions options = monitor.Get(builder.Name); + GrpcClientFactoryOptions factoryOptions = monitor.Get(builder.Name); - Assert.Equal(new Uri($"http://localhost:{port}"), options.Address); + Assert.Equal(new Uri($"http://localhost:{port}"), factoryOptions.Address); Assert.Null(handler); + + if (maxMessageLength is int expectedLength) + { + GrpcChannelOptions channelOptions = new(); + foreach (Action action in factoryOptions.ChannelOptionsActions) + { + action(channelOptions); + } + + Assert.Equal(expectedLength, channelOptions.MaxReceiveMessageSize); + Assert.Equal(expectedLength, channelOptions.MaxSendMessageSize); + } } private class CallInvokerExtractor diff --git a/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs new file mode 100644 index 000000000..71a5c394c --- /dev/null +++ b/test/extensions/Worker.Extensions.Http.AspNetCore.Tests/HttpResultAttributeExpectedTests.cs @@ -0,0 +1,365 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using AnalyzerTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest; +using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; +using CodeFixTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest; +using CodeFixVerifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier; +using Microsoft.CodeAnalysis.Testing; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Tests +{ + public class HttpResultAttributeExpectedTests + { + [Fact] + public async Task HttpResultAttribute_WhenUsingIActionResultAndMultiOutput_Expected() + { + string testCode = @" + using System; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.Functions.Worker; + + namespace AspNetIntegration + { + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new NotImplementedException(); + } + public class MyOutputType + { + public IActionResult 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.MultipleOutputHttpTriggerWithoutHttpResultAttribute) + .WithSeverity(DiagnosticSeverity.Error) + .WithLocation(12, 28) + .WithArguments("\"MultipleOutputBindings\"")); + + await test.RunAsync(); + } + + [Fact] + public async Task HttpResultAttributeUsedCorrectly_NoDiagnostic() + { + string testCode = @" + using System; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.Functions.Worker; + + namespace AspNetIntegration + { + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new NotImplementedException(); + } + public class MyOutputType + { + [HttpResult] + public IActionResult Result { get; set; } + + [BlobOutput(""test-samples-output/{name}-output.txt"")] + public string MessageText { get; set; } + } + } + }"; + + var test = new AnalyzerTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SimpleHttpTrigger_NoDiagnostic() + { + string testCode = @" + using System; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.Functions.Worker; + + namespace AspNetIntegration + { + public class MultipleOutputBindings + { + [Function(""SimpleHttpTrigger"")] + public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new NotImplementedException(); + } + } + }"; + + var test = new AnalyzerTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Fact] + public async Task PocoUsedWithoutOutputBindings_NoDiagnostic() + { + string testCode = @" + using System; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.Functions.Worker; + + namespace AspNetIntegration + { + public class MultipleOutputBindings + { + [Function(""PocoOutput"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new NotImplementedException(); + } + public class MyOutputType + { + public string Name { get; set; } + + public string MessageText { get; set; } + } + } + }"; + + var test = new AnalyzerTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Fact] + public async Task HttpResultAttributeWarning_WhenUsingHttpResponseDataAndMultiOutput_Expected() + { + string testCode = @" + using System; + using Microsoft.AspNetCore.Http; + using Microsoft.Azure.Functions.Worker.Http; + using Microsoft.Azure.Functions.Worker; + + namespace AspNetIntegration + { + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new 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, 28) + .WithArguments("\"MultipleOutputBindings\"")); + + await test.RunAsync(); + } + + [Fact] + public async Task HttpResultAttributeExpected_CodeFixWorks() + { + string inputCode = @" +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; + +namespace AspNetIntegration +{ + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new NotImplementedException(); + } + public class MyOutputType + { + public IActionResult Result { get; set; } + + [BlobOutput(""test-samples-output/{name}-output.txt"")] + public string MessageText { get; set; } + } + } +}"; + + string expectedCode = @" +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; + +namespace AspNetIntegration +{ + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequest req) + { + throw new NotImplementedException(); + } + public class MyOutputType + { + [HttpResult] + public IActionResult Result { get; set; } + + [BlobOutput(""test-samples-output/{name}-output.txt"")] + public string MessageText { get; set; } + } + } +}"; + + + var expectedDiagnosticResult = CodeFixVerifier + .Diagnostic("AZFW0015") + .WithSeverity(DiagnosticSeverity.Error) + .WithLocation(12, 16) + .WithArguments("\"MultipleOutputBindings\""); + + var test = new CodeFixTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = inputCode, + FixedCode = expectedCode + }; + + test.ExpectedDiagnostics.AddRange(new[] { expectedDiagnosticResult }); + await test.RunAsync(); + } + + [Fact] + public async Task HttpResultAttributeForHttpResponseDataExpected_CodeFixWorks() + { + string inputCode = @" +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace AspNetIntegration +{ + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequestData req) + { + throw new 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; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace AspNetIntegration +{ + public class MultipleOutputBindings + { + [Function(""MultipleOutputBindings"")] + public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, ""post"")] HttpRequestData req) + { + throw new 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(13, 16) + .WithArguments("\"MultipleOutputBindings\""); + + var test = new CodeFixTest + { + ReferenceAssemblies = LoadRequiredDependencyAssemblies(), + TestCode = inputCode, + FixedCode = expectedCode + }; + + test.ExpectedDiagnostics.AddRange(new[] { expectedDiagnosticResult }); + await test.RunAsync(); + } + + private static ReferenceAssemblies LoadRequiredDependencyAssemblies() + { + var referenceAssemblies = ReferenceAssemblies.Net.Net60.WithPackages(ImmutableArray.Create( + new PackageIdentity("Microsoft.Azure.Functions.Worker", "1.22.0"), + new PackageIdentity("Microsoft.Azure.Functions.Worker.Sdk", "1.17.4"), + new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs", "6.0.0"), + new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore", "1.3.2"), + new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Abstractions", "5.0.0"), + new PackageIdentity("Microsoft.AspNetCore.Mvc.Core", "2.2.5"), + new PackageIdentity("Microsoft.Extensions.Hosting.Abstractions", "6.0.0"), + new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.Http", "3.2.0"))); + + return referenceAssemblies; + } + } +} 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 8442b9c9d..271194538 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 @@ -29,6 +29,7 @@ +