diff --git a/DotNetWorker.sln b/DotNetWorker.sln index 102591a1b..d413bddc8 100644 --- a/DotNetWorker.sln +++ b/DotNetWorker.sln @@ -118,8 +118,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Tables", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Shared", "extensions\Worker.Extensions.Shared\Worker.Extensions.Shared.csproj", "{277D77B9-8915-41E3-8763-0B66328ADDDA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerBindingSamples", "samples\WorkerBindingSamples\WorkerBindingSamples.csproj", "{901DA3C3-3ABA-4859-89D3-63343ED2A0AC}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Rpc", "extensions\Worker.Extensions.Rpc\src\Worker.Extensions.Rpc.csproj", "{93B64F12-EBDD-46CE-B4FB-0904701F0032}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{AA4D318D-101B-49E7-A4EC-B34165AEDB33}" @@ -135,6 +133,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetIntegration", "samples\AspNetIntegration\AspNetIntegration.csproj", "{D2F67410-9933-42E8-B04A-E17634D83A30}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net7Worker", "samples\Net7Worker\Net7Worker.csproj", "{EC1A321B-70F6-420B-85D4-56C7869BB71B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependentAssemblyWithFunctions", "test\DependentAssemblyWithFunctions\DependentAssemblyWithFunctions.csproj", "{AB6E1E7A-0D2C-4086-9487-566887C1E753}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -306,10 +305,6 @@ Global {277D77B9-8915-41E3-8763-0B66328ADDDA}.Debug|Any CPU.Build.0 = Debug|Any CPU {277D77B9-8915-41E3-8763-0B66328ADDDA}.Release|Any CPU.ActiveCfg = Release|Any CPU {277D77B9-8915-41E3-8763-0B66328ADDDA}.Release|Any CPU.Build.0 = Release|Any CPU - {901DA3C3-3ABA-4859-89D3-63343ED2A0AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {901DA3C3-3ABA-4859-89D3-63343ED2A0AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {901DA3C3-3ABA-4859-89D3-63343ED2A0AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {901DA3C3-3ABA-4859-89D3-63343ED2A0AC}.Release|Any CPU.Build.0 = Release|Any CPU {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Debug|Any CPU.Build.0 = Debug|Any CPU {93B64F12-EBDD-46CE-B4FB-0904701F0032}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -338,6 +333,10 @@ Global {EC1A321B-70F6-420B-85D4-56C7869BB71B}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC1A321B-70F6-420B-85D4-56C7869BB71B}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC1A321B-70F6-420B-85D4-56C7869BB71B}.Release|Any CPU.Build.0 = Release|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -388,7 +387,6 @@ Global {65DE66B6-568F-46AC-8F0D-C79A02F48214} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3} {004DEF24-7EBB-499D-BD1C-E940AC4E122D} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} {277D77B9-8915-41E3-8763-0B66328ADDDA} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} - {901DA3C3-3ABA-4859-89D3-63343ED2A0AC} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} {93B64F12-EBDD-46CE-B4FB-0904701F0032} = {A7B4FF1E-3DF7-4F28-9333-D0961CDDF702} {AA4D318D-101B-49E7-A4EC-B34165AEDB33} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} {B13C9E5F-0E4B-413E-90AE-20B84B100364} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33} @@ -397,6 +395,7 @@ Global {17BDCE12-6964-4B87-B2AC-68CE270A3E9A} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} {D2F67410-9933-42E8-B04A-E17634D83A30} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} {EC1A321B-70F6-420B-85D4-56C7869BB71B} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {AB6E1E7A-0D2C-4086-9487-566887C1E753} = {B5821230-6E0A-4535-88A9-ED31B6F07596} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A} diff --git a/build/pipelines/templates/extensions-variables.yml b/build/pipelines/templates/extensions-variables.yml index 06e79d31f..7bec9d1d3 100644 --- a/build/pipelines/templates/extensions-variables.yml +++ b/build/pipelines/templates/extensions-variables.yml @@ -1,10 +1,10 @@ - variables: - ${{ if not(contains(variables['Build.SourceBranch'], '/release/' )) }}: - buildNumberTemp: $(Build.BuildNumber) - ${{ if contains(variables['Build.SourceBranch'], '/release/' ) }}: - isReleaseBuildTemp: true - buildNumber: $[variables.buildNumberTemp] - isReleaseBuild: $[variables.isReleaseBuildTemp] - solution: '**/*.sln' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' \ No newline at end of file +variables: + ${{ if and( not(contains(variables['Build.SourceBranch'], '/release/')), not(startsWith(variables['Build.SourceBranch'], 'refs/tags')) ) }}: + buildNumberTemp: $(Build.BuildNumber) + ${{ if or( contains(variables['Build.SourceBranch'], '/release/'), startsWith(variables['Build.SourceBranch'], 'refs/tags') ) }}: + isReleaseBuildTemp: true + buildNumber: $[variables.buildNumberTemp] + isReleaseBuild: $[variables.isReleaseBuildTemp] + solution: '**/*.sln' + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' diff --git a/docs/analyzer-rules/AZFW0013.md b/docs/analyzer-rules/AZFW0013.md new file mode 100644 index 000000000..c09223c31 --- /dev/null +++ b/docs/analyzer-rules/AZFW0013.md @@ -0,0 +1,25 @@ +# AZFW0013: Unable to parse binding argument + +| | Value | +|-|-| +| **Rule ID** |AZFW0013| +| **Category** |[AzureFunctionsSyntax]| +| **Severity** |Error| + +## Cause + +This rule is triggered when a binding attribute argument is an invalid or null value. + +## Rule description + +[Attributes](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-attributes/) are used to define bindings, and the function metadata generator parses the arguments passed into these attributes to generate binding information during start up. + +If the arguments passed in are invalid for any reason (such as being null), this rule is enforced. + +## How to fix violations + +Review the binding attribute. + +## When to suppress warnings + +This rule should not be suppressed because this error will prevent your functions from running. diff --git a/extensions/Worker.Extensions.CosmosDB/release_notes.md b/extensions/Worker.Extensions.CosmosDB/release_notes.md index bd526fa52..e514b397c 100644 --- a/extensions/Worker.Extensions.CosmosDB/release_notes.md +++ b/extensions/Worker.Extensions.CosmosDB/release_notes.md @@ -4,6 +4,7 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB 4.4.1 +### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB 4.5.0 -- [Bug] Update CosmosOptions from using IOptionsSnapshot to IOptionsMonitor to avoid DI scoping issues +- Updated `Microsoft.Azure.WebJobs.Extensions.CosmosDB` to 4.4.0 + - Release notes for v4.4.0 can be found [here](https://github.com/Azure/azure-webjobs-sdk-extensions/releases/tag/cosmos-v4.4.0) diff --git a/extensions/Worker.Extensions.CosmosDB/src/Config/CosmosDBBindingOptionsSetup.cs b/extensions/Worker.Extensions.CosmosDB/src/Config/CosmosDBBindingOptionsSetup.cs index a7bebfd56..294efb3ad 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/Config/CosmosDBBindingOptionsSetup.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/Config/CosmosDBBindingOptionsSetup.cs @@ -14,11 +14,13 @@ internal class CosmosDBBindingOptionsSetup : IConfigureNamedOptions _workerOptions; - public CosmosDBBindingOptionsSetup(IConfiguration configuration, AzureComponentFactory componentFactory) + public CosmosDBBindingOptionsSetup(IConfiguration configuration, AzureComponentFactory componentFactory, IOptionsMonitor workerOptions) { _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); _componentFactory = componentFactory ?? throw new ArgumentNullException(nameof(componentFactory)); + _workerOptions = workerOptions ?? throw new ArgumentNullException(nameof(workerOptions)); } public void Configure(CosmosDBBindingOptions options) @@ -53,6 +55,8 @@ public void Configure(string connectionName, CosmosDBBindingOptions options) options.Credential = _componentFactory.CreateTokenCredential(connectionSection); } + + options.Serializer = new WorkerCosmosSerializer(_workerOptions.CurrentValue.Serializer); } } } \ No newline at end of file diff --git a/extensions/Worker.Extensions.CosmosDB/src/CosmosExtensionStartup.cs b/extensions/Worker.Extensions.CosmosDB/src/CosmosExtensionStartup.cs index 4ee60b670..59f56d027 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/CosmosExtensionStartup.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/CosmosExtensionStartup.cs @@ -2,12 +2,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using Microsoft.Azure.Cosmos; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Core; -using Microsoft.Extensions.Azure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; [assembly: WorkerExtensionStartup(typeof(CosmosExtensionStartup))] @@ -22,16 +18,7 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui throw new ArgumentNullException(nameof(applicationBuilder)); } - applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory - - applicationBuilder.Services.AddOptions() - .Configure>((cosmosOptions, workerOptions) => - { - CosmosSerializer cosmosSerializer = new WorkerCosmosSerializer(workerOptions?.Value?.Serializer); - cosmosOptions.Serializer = cosmosSerializer; - }); - - applicationBuilder.Services.AddSingleton, CosmosDBBindingOptionsSetup>(); + applicationBuilder.ConfigureCosmosDBExtension(); } } } diff --git a/extensions/Worker.Extensions.CosmosDB/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs b/extensions/Worker.Extensions.CosmosDB/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs new file mode 100644 index 000000000..fc904ef25 --- /dev/null +++ b/extensions/Worker.Extensions.CosmosDB/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs @@ -0,0 +1,35 @@ +// 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.Azure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// Provides extension methods to work with a . + /// + public static class FunctionsWorkerApplicationBuilderExtensions + { + /// + /// Configures the CosmosDB extension. + /// + /// The to configure. + /// The same instance of the for chaining. + public static IFunctionsWorkerApplicationBuilder ConfigureCosmosDBExtension(this IFunctionsWorkerApplicationBuilder builder) + { + if (builder is null) + { + throw new System.ArgumentNullException(nameof(builder)); + } + + builder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory + builder.Services.AddOptions(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, CosmosDBBindingOptionsSetup>()); + + return builder; + } + } +} diff --git a/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs index 5d16f5c3f..2ea9727ad 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.CosmosDB/src/Properties/AssemblyInfo.cs @@ -4,6 +4,6 @@ using System.Runtime.CompilerServices; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.CosmosDB", "4.3.0")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.CosmosDB", "4.4.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 2bf9a98f3..a1e7997b4 100644 --- a/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj +++ b/extensions/Worker.Extensions.CosmosDB/src/Worker.Extensions.CosmosDB.csproj @@ -6,7 +6,7 @@ Azure Cosmos DB extensions for .NET isolated functions - 4.4.1 + 4.5.0 false diff --git a/extensions/Worker.Extensions.EventGrid/release_notes.md b/extensions/Worker.Extensions.EventGrid/release_notes.md index 60f407532..9e82bcaf3 100644 --- a/extensions/Worker.Extensions.EventGrid/release_notes.md +++ b/extensions/Worker.Extensions.EventGrid/release_notes.md @@ -6,4 +6,4 @@ ### Microsoft.Azure.Functions.Worker.Extensions.EventGrid -- +- diff --git a/extensions/Worker.Extensions.EventGrid/src/EventGridOutputAttribute.cs b/extensions/Worker.Extensions.EventGrid/src/EventGridOutputAttribute.cs index a93b00d90..6494a0c19 100644 --- a/extensions/Worker.Extensions.EventGrid/src/EventGridOutputAttribute.cs +++ b/extensions/Worker.Extensions.EventGrid/src/EventGridOutputAttribute.cs @@ -21,5 +21,10 @@ public EventGridOutputAttribute() /// Gets or sets the Topic Key setting. You can find information on getting the Key for a topic here: https://docs.microsoft.com/en-us/azure/event-grid/custom-event-quickstart#send-an-event-to-your-topic public string? TopicKeySetting { get; set; } + + /// + /// Gets or sets the app setting name that contains the Event Grid topic's connection information. When setting the `Connection` property, the `TopicEndpointUri` and `TopicKeySetting` properties should NOT be set. + /// + public string? Connection { get; set; } } } diff --git a/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj b/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj index 476552aec..e340ab872 100644 --- a/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj +++ b/extensions/Worker.Extensions.EventGrid/src/Worker.Extensions.EventGrid.csproj @@ -6,7 +6,7 @@ Azure Event Grid extensions for .NET isolated functions - 3.3.0 + 3.4.0 false diff --git a/extensions/Worker.Extensions.EventHubs/release_notes.md b/extensions/Worker.Extensions.EventHubs/release_notes.md index 3d6e918a8..d3055b4f2 100644 --- a/extensions/Worker.Extensions.EventHubs/release_notes.md +++ b/extensions/Worker.Extensions.EventHubs/release_notes.md @@ -6,4 +6,4 @@ ### Microsoft.Azure.Functions.Worker.Extensions.EventHubs -- +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs index 32037f4c0..bd052b1e8 100644 --- a/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.EventHubs/src/Properties/AssemblyInfo.cs @@ -4,5 +4,5 @@ using System.Runtime.CompilerServices; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.EventHubs", "5.4.0")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.EventHubs", "5.5.0")] [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 6c655a3cf..d77620fde 100644 --- a/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj +++ b/extensions/Worker.Extensions.EventHubs/src/Worker.Extensions.EventHubs.csproj @@ -6,7 +6,7 @@ Azure Event Hubs extensions for .NET isolated functions - 5.5.0 + 5.6.0 diff --git a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md index 18f7d2427..7b0801352 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md +++ b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore +### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore -- Adding support for binding to request body (`FromBodyAttribute` support) +- diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj b/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj index 7112ad9a0..5718e34b8 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj @@ -7,7 +7,6 @@ 1.0.0 - -preview4 net6.0 diff --git a/extensions/Worker.Extensions.Kafka/release_notes.md b/extensions/Worker.Extensions.Kafka/release_notes.md index c000924da..ca17cb549 100644 --- a/extensions/Worker.Extensions.Kafka/release_notes.md +++ b/extensions/Worker.Extensions.Kafka/release_notes.md @@ -4,8 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Kafka 3.9.0 +### Microsoft.Azure.Functions.Worker.Extensions.Kafka -- Add `BindingCapabilities` attribute to KafkaTrigger to express function-level retry capabilities. (#1457) -- Updated Kafka Extension nuget package to most recent version (3.9.0) (#1713) -- Exposed an important scaling parameter `LagThreshold`` to let a user configure their scaling preferences within Kafka Trigger function. (#1713) +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.Rpc/release_notes.md b/extensions/Worker.Extensions.Rpc/release_notes.md index fdf74f389..ddca44e2c 100644 --- a/extensions/Worker.Extensions.Rpc/release_notes.md +++ b/extensions/Worker.Extensions.Rpc/release_notes.md @@ -4,12 +4,7 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.0-preview1 +### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.0 -- Add `IHttpClientBuilder.ConfigureForFunctionsHostGrpc` extension method. (#1616) - - Used along with `IServiceCollection.AddGrpcClient()` to configure for Functions host communication. - - **NOTE**: when using Grpc.Net.ClientFactory <= 2.54.0-pre1 use `IServiceCollection.AddGrpcClient(_ => { })` - - See [grpc/grpc-dotnet/issues/2158](https://github.com/grpc/grpc-dotnet/issues/2158) - - Available in `>=net6.0` only. -- Add `FunctionsGrpcOptions`, which can be used to get a built `CallInvoker` for gRPC communication with the functions host. (#1637) - - Available in all TFMs. \ No newline at end of file +- Initial public release +- Adds API for getting a `CallInvoker` pre-configured for communication with Functions host. \ No newline at end of file diff --git a/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs b/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs index 3c3d64415..cb2bc2132 100644 --- a/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs +++ b/extensions/Worker.Extensions.Rpc/src/ConfigurationExtensions.cs @@ -18,10 +18,22 @@ internal static class ConfigurationExtensions /// public static Uri GetFunctionsHostGrpcUri(this IConfiguration configuration) { - string uriString = $"http://{configuration["HOST"]}:{configuration["PORT"]}"; - if (!Uri.TryCreate(uriString, UriKind.Absolute, out Uri? grpcUri)) + Uri? grpcUri; + var functionsUri = configuration["Functions:Worker:HostEndpoint"]; + if (functionsUri is not null) { - throw new InvalidOperationException($"The gRPC channel URI '{uriString}' could not be parsed."); + if (!Uri.TryCreate(functionsUri, UriKind.Absolute, out grpcUri)) + { + throw new InvalidOperationException($"The gRPC channel URI '{functionsUri}' could not be parsed."); + } + } + else + { + var uriString = $"http://{configuration["HOST"]}:{configuration["PORT"]}"; + if (!Uri.TryCreate(uriString, UriKind.Absolute, out grpcUri)) + { + throw new InvalidOperationException($"The gRPC channel URI '{uriString}' could not be parsed."); + } } return grpcUri; diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs new file mode 100644 index 000000000..ff0780c38 --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs @@ -0,0 +1,55 @@ +// 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 Grpc.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc +{ + /// + /// extensions for RPC. + /// + public static partial class RpcServiceCollectionExtensions + { + 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.TryAddEnumerable( + ServiceDescriptor.Transient, ConfigureOptions>()); + } + + // Used as a roundabout way of getting the configured CallInvoker from Grpc.Net.ClientFactory. + private class CallInvokerExtractor + { + public CallInvokerExtractor(CallInvoker callInvoker) + { + CallInvoker = callInvoker ?? throw new ArgumentNullException(nameof(callInvoker)); + } + + public CallInvoker CallInvoker { get; } + } + + private class ConfigureOptions : IConfigureOptions + { + private readonly CallInvokerExtractor _extractor; + + public ConfigureOptions(CallInvokerExtractor extractor) + { + _extractor = extractor; + } + + public void Configure(FunctionsGrpcOptions options) + { + options.CallInvoker = _extractor.CallInvoker; + } + } + } +} +#endif diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs new file mode 100644 index 000000000..743473bc2 --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs @@ -0,0 +1,43 @@ +// 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 Grpc.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc +{ + /// + /// extensions for RPC. + /// + public static partial class RpcServiceCollectionExtensions + { + private static void ConfigureCallInvoker(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, ConfigureOptions>()); + } + + private class ConfigureOptions : IConfigureOptions + { + private readonly IConfiguration _configuration; + + public ConfigureOptions(IConfiguration configuration) + { + _configuration = configuration; + } + + public void Configure(FunctionsGrpcOptions options) + { + Uri address = _configuration.GetFunctionsHostGrpcUri(); + Channel c = new Channel(address.Host, address.Port, ChannelCredentials.Insecure); + options.CallInvoker = c.CreateCallInvoker(); + } + } + } +} +#endif diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.cs new file mode 100644 index 000000000..9aaab718b --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.cs @@ -0,0 +1,33 @@ +// 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.Extensions.DependencyInjection; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc +{ + /// + /// extensions for RPC. + /// + public static partial class RpcServiceCollectionExtensions + { + /// + /// Adds requires types for Functions host and worker RPC communication. Extensions that want to use RPC are + /// expected to call this before configuring their RPC clients. + /// + /// The service collection. + /// The original service collection for call chaining. + public static IServiceCollection AddWorkerRpc(this IServiceCollection services) + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddOptions() + .Validate(options => options.CallInvoker is not null, "gRPC CallInvoker must not be null."); + ConfigureCallInvoker(services); + return services; + } + } +} diff --git a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj index 6dd028423..3ff8a7ce9 100644 --- a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj +++ b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj @@ -6,7 +6,6 @@ Microsoft.Azure.Functions.Worker.Extensions.Rpc Contains types to facilitate RPC communication between a worker extension and the functions host. 1.0.0 - -preview1 README.md diff --git a/extensions/Worker.Extensions.Rpc/src/WorkerRpcStartup.cs b/extensions/Worker.Extensions.Rpc/src/WorkerRpcStartup.cs deleted file mode 100644 index df159e365..000000000 --- a/extensions/Worker.Extensions.Rpc/src/WorkerRpcStartup.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using Grpc.Core; -using Microsoft.Azure.Functions.Worker.Core; -using Microsoft.Azure.Functions.Worker.Extensions.Rpc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -#if NETSTANDARD -using Microsoft.Extensions.Configuration; -#endif - -[assembly: WorkerExtensionStartup(typeof(WorkerRpcStartup))] - -namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc -{ - - /// - /// Startup code for extensions RPC. - /// - public sealed class WorkerRpcStartup : WorkerExtensionStartup - { - /// - public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder) - { - if (applicationBuilder is null) - { - throw new ArgumentNullException(nameof(applicationBuilder)); - } - - OptionsBuilder builder = applicationBuilder.Services - .AddOptions(); - - ConfigureCallInvoker(builder); - builder.Validate(options => options.CallInvoker is not null, "gRPC CallInvoker must not be null."); - } - - private void ConfigureCallInvoker(OptionsBuilder builder) - { -#if NETSTANDARD - builder.Configure((options, config) => - { - Uri address = config.GetFunctionsHostGrpcUri(); - Channel c = new Channel(address.Host, address.Port, ChannelCredentials.Insecure); - options.CallInvoker = c.CreateCallInvoker(); - }); - -#else - // 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. - builder.Services.AddGrpcClient(_ => { }) - .ConfigureForFunctionsHostGrpc(); - - builder.Configure((options, extractor) => - { - options.CallInvoker = extractor.CallInvoker; - }); -#endif - } - -#if !NETSTANDARD - // Used as a roundabout way of getting the configured CallInvoker from Grpc.Net.ClientFactory. - private class CallInvokerExtractor - { - public CallInvokerExtractor(CallInvoker callInvoker) - { - CallInvoker = callInvoker ?? throw new ArgumentNullException(nameof(callInvoker)); - } - - public CallInvoker CallInvoker { get; } - } -#endif - } -} diff --git a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs index 77bee5443..26b1b7c2e 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/Properties/AssemblyInfo.cs @@ -4,5 +4,5 @@ using System.Runtime.CompilerServices; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.11.0")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.ServiceBus", "5.12.0")] [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 47cc227d8..b08a98d49 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj +++ b/extensions/Worker.Extensions.ServiceBus/src/Worker.Extensions.ServiceBus.csproj @@ -6,7 +6,7 @@ Azure Service Bus extensions for .NET isolated functions - 5.12.0 + 5.13.0 false diff --git a/extensions/Worker.Extensions.SignalRService/release_notes.md b/extensions/Worker.Extensions.SignalRService/release_notes.md index d957a5aad..0198f2ccf 100644 --- a/extensions/Worker.Extensions.SignalRService/release_notes.md +++ b/extensions/Worker.Extensions.SignalRService/release_notes.md @@ -4,7 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.SignalRService +### Microsoft.Azure.Functions.Worker.Extensions.SignalRService 1.11.0 - Add `ServerlessHub` and `ServerlessHub` for SignalR Service extensions (#771) - diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/StorageExtensionStartup.cs b/extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageExtensionStartup.cs similarity index 50% rename from extensions/Worker.Extensions.Storage.Blobs/src/StorageExtensionStartup.cs rename to extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageExtensionStartup.cs index 9f8b10bb1..acf73961c 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/StorageExtensionStartup.cs +++ b/extensions/Worker.Extensions.Storage.Blobs/src/BlobStorageExtensionStartup.cs @@ -4,15 +4,12 @@ using System; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Core; -using Microsoft.Extensions.Azure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -[assembly: WorkerExtensionStartup(typeof(StorageExtensionStartup))] +[assembly: WorkerExtensionStartup(typeof(BlobStorageExtensionStartup))] namespace Microsoft.Azure.Functions.Worker { - public class StorageExtensionStartup : WorkerExtensionStartup + public class BlobStorageExtensionStartup : WorkerExtensionStartup { public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder) { @@ -21,9 +18,7 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui throw new ArgumentNullException(nameof(applicationBuilder)); } - applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory - applicationBuilder.Services.AddOptions(); - applicationBuilder.Services.AddSingleton, BlobStorageBindingOptionsSetup>(); + applicationBuilder.ConfigureBlobStorageExtension(); } } } diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerAttribute.cs b/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerAttribute.cs index 31182c34f..3a9aa5271 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerAttribute.cs +++ b/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerAttribute.cs @@ -40,5 +40,10 @@ public string BlobPath /// Gets or sets the app setting name that contains the Azure Storage connection string. /// public string? Connection { get; set; } + + /// + /// Gets or sets the blob trigger source used to detect blob changes. Default is . + /// + public BlobTriggerSource Source { get; set; } = BlobTriggerSource.LogsAndContainerScan; } } diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerSource.cs b/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerSource.cs new file mode 100644 index 000000000..cc86d4d1f --- /dev/null +++ b/extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerSource.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// Specifies the blob trigger source used to detect blob changes. + /// + public enum BlobTriggerSource + { + /// + /// Polling works as a hybrid between inspecting logs and running periodic container scans. Blobs are scanned in groups of 10,000 at a time with a continuation token used between intervals. + /// Storage Analytics logs + /// + LogsAndContainerScan, + /// + /// Uses Event Grid as the source of change notifications. + /// Azure Blob Storage as an Event Grid source + /// + EventGrid + } +} diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs b/extensions/Worker.Extensions.Storage.Blobs/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs new file mode 100644 index 000000000..140360397 --- /dev/null +++ b/extensions/Worker.Extensions.Storage.Blobs/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs @@ -0,0 +1,35 @@ +// 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.Azure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// Provides extension methods to work with a . + /// + public static class FunctionsWorkerApplicationBuilderExtensions + { + /// + /// Configures the CosmosDB extension. + /// + /// The to configure. + /// The same instance of the for chaining. + public static IFunctionsWorkerApplicationBuilder ConfigureBlobStorageExtension(this IFunctionsWorkerApplicationBuilder builder) + { + if (builder is null) + { + throw new System.ArgumentNullException(nameof(builder)); + } + + builder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory + builder.Services.AddOptions(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, BlobStorageBindingOptionsSetup>()); + + return builder; + } + } +} diff --git a/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs b/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs index e8d3e501f..6079dd1c6 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs +++ b/extensions/Worker.Extensions.Storage.Blobs/src/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using Microsoft.Azure.Functions.Worker.Extensions.Abstractions; -[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs", "5.1.3")] +[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs", "5.2.0")] [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 0c3476bfc..9466facf5 100644 --- a/extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj +++ b/extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj @@ -6,7 +6,7 @@ Azure Blob Storage extensions for .NET isolated functions - 6.0.1 + 6.2.0 false diff --git a/extensions/Worker.Extensions.Storage/release_notes.md b/extensions/Worker.Extensions.Storage/release_notes.md index ec4a1d938..b47906b12 100644 --- a/extensions/Worker.Extensions.Storage/release_notes.md +++ b/extensions/Worker.Extensions.Storage/release_notes.md @@ -4,15 +4,14 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Storage 6.0.1 +### Microsoft.Azure.Functions.Worker.Extensions.Storage -- Updated Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs dependency to 6.0.1 +- -### Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs 6.0.1 +### Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs -- [Bug] Update BlobOptions from using IOptionsSnapshot to IOptionsMonitor to avoid DI scoping issues +- ### Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues -- - +- \ No newline at end of file diff --git a/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj b/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj index 9ae9070ae..aae129073 100644 --- a/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj +++ b/extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj @@ -6,7 +6,7 @@ Azure Storage extensions for .NET isolated functions - 6.0.1 + 6.2.0 false diff --git a/extensions/Worker.Extensions.Tables/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs b/extensions/Worker.Extensions.Tables/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs new file mode 100644 index 000000000..feaed2d45 --- /dev/null +++ b/extensions/Worker.Extensions.Tables/src/Extensions/FunctionsWorkerApplicationBuilderExtensions.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 Microsoft.Azure.Functions.Worker.Extensions.Tables.Config; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// Provides extension methods to work with a . + /// + public static class FunctionsWorkerApplicationBuilderExtensions + { + /// + /// Configures the CosmosDB extension. + /// + /// The to configure. + /// The same instance of the for chaining. + public static IFunctionsWorkerApplicationBuilder ConfigureTablesExtension(this IFunctionsWorkerApplicationBuilder builder) + { + if (builder is null) + { + throw new System.ArgumentNullException(nameof(builder)); + } + + builder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory + builder.Services.AddOptions(); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, TablesBindingOptionsSetup>()); + + return builder; + } + } +} diff --git a/extensions/Worker.Extensions.Tables/src/TableExtensionStartup.cs b/extensions/Worker.Extensions.Tables/src/TableExtensionStartup.cs index d20fd8d32..9fadfe053 100644 --- a/extensions/Worker.Extensions.Tables/src/TableExtensionStartup.cs +++ b/extensions/Worker.Extensions.Tables/src/TableExtensionStartup.cs @@ -4,10 +4,6 @@ using System; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Core; -using Microsoft.Azure.Functions.Worker.Extensions.Tables.Config; -using Microsoft.Extensions.Azure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; [assembly: WorkerExtensionStartup(typeof(TableExtensionStartup))] @@ -28,9 +24,7 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui throw new ArgumentNullException(nameof(applicationBuilder)); } - applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory - applicationBuilder.Services.AddOptions(); - applicationBuilder.Services.AddSingleton, TablesBindingOptionsSetup>(); + applicationBuilder.ConfigureTablesExtension(); } } } diff --git a/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj b/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj index c6d1d9d7c..be07646d0 100644 --- a/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj +++ b/extensions/Worker.Extensions.Tables/src/Worker.Extensions.Tables.csproj @@ -6,7 +6,7 @@ Azure Table Storage extensions for .NET isolated functions - 1.2.0 + 1.2.1 diff --git a/host/src/FunctionsNetHost/Grpc/GrpcClient.cs b/host/src/FunctionsNetHost/Grpc/GrpcClient.cs index bbc1f7167..c910c645f 100644 --- a/host/src/FunctionsNetHost/Grpc/GrpcClient.cs +++ b/host/src/FunctionsNetHost/Grpc/GrpcClient.cs @@ -33,7 +33,7 @@ internal GrpcClient(GrpcWorkerStartupOptions grpcWorkerStartupOptions, AppLoader internal async Task InitAsync() { - var endpoint = $"http://{_grpcWorkerStartupOptions.Host}:{_grpcWorkerStartupOptions.Port}"; + var endpoint = _grpcWorkerStartupOptions.ServerUri.AbsoluteUri; Logger.LogTrace($"Grpc service endpoint:{endpoint}"); var functionRpcClient = CreateFunctionRpcClient(endpoint); diff --git a/host/src/FunctionsNetHost/Grpc/GrpcWorkerStartupOptions.cs b/host/src/FunctionsNetHost/Grpc/GrpcWorkerStartupOptions.cs index 3ecf513b8..0cc8fd292 100644 --- a/host/src/FunctionsNetHost/Grpc/GrpcWorkerStartupOptions.cs +++ b/host/src/FunctionsNetHost/Grpc/GrpcWorkerStartupOptions.cs @@ -5,9 +5,7 @@ namespace FunctionsNetHost.Grpc { internal sealed class GrpcWorkerStartupOptions { - public string? Host { get; set; } - - public int Port { get; set; } + public Uri ServerUri { get; set; } public string? WorkerId { get; set; } diff --git a/host/src/FunctionsNetHost/Program.cs b/host/src/FunctionsNetHost/Program.cs index 3f8def5eb..83bf2a976 100644 --- a/host/src/FunctionsNetHost/Program.cs +++ b/host/src/FunctionsNetHost/Program.cs @@ -16,7 +16,6 @@ static async Task Main(string[] args) var workerStartupOptions = await GetStartupOptionsFromCmdLineArgs(args); - using var appLoader = new AppLoader(); var grpcClient = new GrpcClient(workerStartupOptions, appLoader); @@ -30,30 +29,36 @@ static async Task Main(string[] args) private static async Task GetStartupOptionsFromCmdLineArgs(string[] args) { - var hostOption = new Option("--host"); - var portOption = new Option("--port"); - var workerOption = new Option("--workerId"); - var grpcMsgLengthOption = new Option("--grpcMaxMessageLength"); - var requestIdOption = new Option("--requestId"); + var uriOption = new Option("--functions-uri") { IsRequired = true }; + var requestIdOption = new Option("--functions-request-id") { IsRequired = true }; + var workerIdOption = new Option("--functions-worker-id") { IsRequired = true }; + var grpcMaxMessageLengthOption = new Option("--functions-grpc-max-message-length") { IsRequired = true }; + + var rootCommand = new RootCommand + { + TreatUnmatchedTokensAsErrors = false + }; - var rootCommand = new RootCommand(); - rootCommand.AddOption(portOption); - rootCommand.AddOption(hostOption); - rootCommand.AddOption(workerOption); - rootCommand.AddOption(grpcMsgLengthOption); + rootCommand.AddOption(uriOption); rootCommand.AddOption(requestIdOption); + rootCommand.AddOption(workerIdOption); + rootCommand.AddOption(grpcMaxMessageLengthOption); var workerStartupOptions = new GrpcWorkerStartupOptions(); - rootCommand.SetHandler((host, port, workerId, grpcMsgLength, requestId) => + rootCommand.SetHandler((context) => { - workerStartupOptions.Host = host; - workerStartupOptions.Port = port; - workerStartupOptions.WorkerId = workerId; - workerStartupOptions.GrpcMaxMessageLength = grpcMsgLength; - workerStartupOptions.RequestId = requestId; - }, - hostOption, portOption, workerOption, grpcMsgLengthOption, requestIdOption); + var uriString = context.ParseResult.GetValueForOption(uriOption); + if (!Uri.TryCreate(uriString, UriKind.Absolute, out var endpointUri)) + { + throw new UriFormatException($"'{uriString}' is not a valid value for argument '{uriOption.Name}'. Value should be a valid URL."); + } + + workerStartupOptions.ServerUri = endpointUri; + workerStartupOptions.GrpcMaxMessageLength = context.ParseResult.GetValueForOption(grpcMaxMessageLengthOption); + workerStartupOptions.RequestId = context.ParseResult.GetValueForOption(requestIdOption); + workerStartupOptions.WorkerId = context.ParseResult.GetValueForOption(workerIdOption); + }); Logger.LogTrace($"raw args:{string.Join(" ", args)}"); diff --git a/host/src/FunctionsNetHost/global.json b/host/src/FunctionsNetHost/global.json index e90889403..f735d6ed6 100644 --- a/host/src/FunctionsNetHost/global.json +++ b/host/src/FunctionsNetHost/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100-preview.6.23330.14", + "version": "8.0.100-rc.1.23455.8", "allowPrerelease": true, "rollForward": "latestMinor" } diff --git a/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec b/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec index 2fe6ae20c..c2ab2b44b 100644 --- a/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec +++ b/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec @@ -4,7 +4,7 @@ Microsoft.Azure.Functions.DotNetIsolatedNativeHost Microsoft Azure Functions dotnet-isolated native host dotnet-isolated azure-functions azure - 1.0.0-preview805 + 1.0.0 Microsoft Microsoft https://github.com/Azure/azure-functions-dotnet-worker diff --git a/release_notes.md b/release_notes.md index 37057e890..d210c4639 100644 --- a/release_notes.md +++ b/release_notes.md @@ -10,8 +10,10 @@ ### Microsoft.Azure.Functions.Worker.Core -- +- Adding optional parameter support (#1868) ### Microsoft.Azure.Functions.Worker.Grpc -- +- Added support for handling the new command line arguments with "functions-" prefix. (#1897) +- Adding optional parameter support (#1868) + \ No newline at end of file diff --git a/samples/Extensions/Blob/BlobFunction.cs b/samples/Extensions/Blob/BlobFunction.cs index cb5b92f0a..32c0fe5a3 100644 --- a/samples/Extensions/Blob/BlobFunction.cs +++ b/samples/Extensions/Blob/BlobFunction.cs @@ -8,7 +8,7 @@ namespace SampleApp { public static class BlobFunction { - [Function("BlobFunction")] + [Function(nameof(BlobFunction))] [BlobOutput("test-samples-output/{name}-output.txt")] public static string Run( [BlobTrigger("test-samples-trigger/{name}")] string myTriggerItem, @@ -16,8 +16,8 @@ public static string Run( FunctionContext context) { var logger = context.GetLogger("BlobFunction"); - logger.LogInformation($"Triggered Item = {myTriggerItem}"); - logger.LogInformation($"Input Item = {myBlob}"); + logger.LogInformation("Triggered Item = {myTriggerItem}", myTriggerItem); + logger.LogInformation("Input Item = {myBlob}", myBlob); // Blob Output return "blob-output content"; diff --git a/samples/WorkerBindingSamples/Blob/BlobInputBindingSamples.cs b/samples/Extensions/Blob/BlobInputBindingSamples.cs similarity index 98% rename from samples/WorkerBindingSamples/Blob/BlobInputBindingSamples.cs rename to samples/Extensions/Blob/BlobInputBindingSamples.cs index 74952c066..ababb03ba 100644 --- a/samples/WorkerBindingSamples/Blob/BlobInputBindingSamples.cs +++ b/samples/Extensions/Blob/BlobInputBindingSamples.cs @@ -1,8 +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 System.Collections.Generic; +using System.IO; using System.Net; -using System.Text; +using System.Threading.Tasks; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Microsoft.Azure.Functions.Worker; diff --git a/samples/WorkerBindingSamples/Blob/BlobTriggerBindingSamples.cs b/samples/Extensions/Blob/BlobTriggerBindingSamples.cs similarity index 98% rename from samples/WorkerBindingSamples/Blob/BlobTriggerBindingSamples.cs rename to samples/Extensions/Blob/BlobTriggerBindingSamples.cs index b9eebaac3..d72f4f3d3 100644 --- a/samples/WorkerBindingSamples/Blob/BlobTriggerBindingSamples.cs +++ b/samples/Extensions/Blob/BlobTriggerBindingSamples.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 System; +using System.IO; using System.Text; +using System.Threading.Tasks; using Azure.Storage.Blobs; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; diff --git a/samples/WorkerBindingSamples/Blob/Book.cs b/samples/Extensions/Blob/Book.cs similarity index 71% rename from samples/WorkerBindingSamples/Blob/Book.cs rename to samples/Extensions/Blob/Book.cs index 7e6d372df..824e88336 100644 --- a/samples/WorkerBindingSamples/Blob/Book.cs +++ b/samples/Extensions/Blob/Book.cs @@ -5,7 +5,7 @@ namespace SampleApp { public class Book { - public string? Id { get; set; } - public string? Name { get; set; } + public string Id { get; set; } + public string Name { get; set; } } } \ No newline at end of file diff --git a/samples/Extensions/CosmosDB/CosmosDBFunction.cs b/samples/Extensions/CosmosDB/CosmosDBFunction.cs index a0c377744..d2b044aa2 100644 --- a/samples/Extensions/CosmosDB/CosmosDBFunction.cs +++ b/samples/Extensions/CosmosDB/CosmosDBFunction.cs @@ -8,24 +8,33 @@ namespace SampleApp { - public static class CosmosDBFunction + public class CosmosDBFunction { + private readonly ILogger _logger; + + public CosmosDBFunction(ILogger logger) + { + _logger = logger; + } + // - [Function("CosmosDBFunction")] + [Function(nameof(CosmosDBFunction))] [ExponentialBackoffRetry(5, "00:00:04", "00:15:00")] - [CosmosDBOutput("%CosmosDb%", "%CosmosCollOut%", ConnectionStringSetting = "CosmosConnection", CreateIfNotExists = true)] - public static object Run( - [CosmosDBTrigger("%CosmosDb%", "%CosmosCollIn%", ConnectionStringSetting = "CosmosConnection", - LeaseCollectionName = "leases", CreateLeaseCollectionIfNotExists = true)] IReadOnlyList input, + [CosmosDBOutput("%CosmosDb%", "%CosmosContainerOut%", Connection = "CosmosDBConnection", CreateIfNotExists = true)] + public object Run( + [CosmosDBTrigger( + "%CosmosDb%", + "%CosmosContainerIn%", + Connection = "CosmosDBConnection", + LeaseContainerName = "leases", + CreateLeaseContainerIfNotExists = true)] IReadOnlyList input, FunctionContext context) { - var logger = context.GetLogger("CosmosDBFunction"); - if (input != null && input.Any()) { foreach (var doc in input) { - logger.LogInformation($"Doc Id: {doc.Id}"); + _logger.LogInformation("Doc Id: {id}", doc.Id); } // Cosmos Output diff --git a/samples/Extensions/CosmosDB/CosmosDbInputBindingFunction.cs b/samples/Extensions/CosmosDB/CosmosDbInputBindingFunction.cs deleted file mode 100644 index 28adcec17..000000000 --- a/samples/Extensions/CosmosDB/CosmosDbInputBindingFunction.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.Logging; - -namespace Extensions.CosmosDB -{ - /// - /// Sample function app which has a azure storage queue trigger and CosmosDb input binding. - /// The CosmosDb input binding is using the values from the queue trigger payload - /// to query the cosmos db item. - /// - - // - public class CosmosDbInputBindingFunction - { - private readonly ILogger _logger; - - public CosmosDbInputBindingFunction(ILogger logger) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - [Function("DocByIdFromJSON")] - public void Run( - [QueueTrigger("todoqueueforlookup")] ToDoItemLookup toDoItemLookup, - [CosmosDBInput(databaseName: "ToDoItems", - collectionName: "Items", - ConnectionStringSetting = "CosmosConnection", - Id ="{ToDoItemId}", - PartitionKey ="{ToDoItemPartitionKeyValue}")] ToDoItem toDoItem) - { - if (toDoItem == null) - { - _logger.LogInformation("ToDo item not found"); - } - else - { - _logger.LogInformation($"Found ToDo item, Description={toDoItem.Description}"); - } - } - } - - public record ToDoItemLookup(string ToDoItemId, string ToDoItemPartitionKeyValue); - // - - // - public class ToDoItem - { - public string Id { get; set; } - public string PartitionKey { get; set; } - public string Description { get; set; } - } - // -} diff --git a/samples/WorkerBindingSamples/Cosmos/CosmosInputBindingFunctions.cs b/samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs similarity index 96% rename from samples/WorkerBindingSamples/Cosmos/CosmosInputBindingFunctions.cs rename to samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs index e5b433b0c..1fcc1ce4c 100644 --- a/samples/WorkerBindingSamples/Cosmos/CosmosInputBindingFunctions.cs +++ b/samples/Extensions/CosmosDB/CosmosInputBindingFunctions.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Net; +using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -27,7 +29,7 @@ public CosmosInputBindingFunctions(ILogger logger) /// The instance could also be used for write operations. /// [Function(nameof(DocsByUsingCosmosClient))] - public async Task DocsByUsingCosmosClient( + public async Task DocsByUsingCosmosClient( [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, [CosmosDBInput(Connection = "CosmosDBConnection")] CosmosClient client) { @@ -80,7 +82,7 @@ public async Task DocsByUsingDatabaseClient( /// as a type. The function then queries for all documents in the collection. /// [Function(nameof(DocsByUsingContainerClient))] - public async Task DocsByUsingContainerClient( + public async Task DocsByUsingContainerClient( [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, [CosmosDBInput("ToDoItems", "Items", Connection = "CosmosDBConnection")] Container container) { @@ -261,11 +263,17 @@ public void DocByIdFromJSON( } } + public class ToDoItem + { + public string Id { get; set; } + public string Description { get; set; } + } + public class ToDoItemLookup { - public string? ToDoItemId { get; set; } + public string ToDoItemId { get; set; } - public string? ToDoItemPartitionKeyValue { get; set; } + public string ToDoItemPartitionKeyValue { get; set; } } } } diff --git a/samples/Extensions/DependencyInjection/DependencyInjectionFunction.cs b/samples/Extensions/DependencyInjection/DependencyInjectionFunction.cs index d7434e3c6..ad51a85e5 100644 --- a/samples/Extensions/DependencyInjection/DependencyInjectionFunction.cs +++ b/samples/Extensions/DependencyInjection/DependencyInjectionFunction.cs @@ -24,7 +24,6 @@ public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "p FunctionContext context) { _logger.LogInformation("message logged"); - return _responderService.ProcessRequest(req); } } diff --git a/samples/WorkerBindingSamples/EventGrid/CloudEventSamples.cs b/samples/Extensions/EventGrid/CloudEventSamples.cs similarity index 100% rename from samples/WorkerBindingSamples/EventGrid/CloudEventSamples.cs rename to samples/Extensions/EventGrid/CloudEventSamples.cs diff --git a/samples/WorkerBindingSamples/EventGrid/EventGridEventSamples.cs b/samples/Extensions/EventGrid/EventGridEventSamples.cs similarity index 100% rename from samples/WorkerBindingSamples/EventGrid/EventGridEventSamples.cs rename to samples/Extensions/EventGrid/EventGridEventSamples.cs diff --git a/samples/Extensions/EventGrid/EventGridFunction.cs b/samples/Extensions/EventGrid/EventGridFunction.cs index a5df5080f..aa1b6e004 100644 --- a/samples/Extensions/EventGrid/EventGridFunction.cs +++ b/samples/Extensions/EventGrid/EventGridFunction.cs @@ -10,11 +10,11 @@ namespace SampleApp { public static class EventGridFunction { - [Function("EventGridFunction")] + [Function(nameof(EventGridFunction))] [EventGridOutput(TopicEndpointUri = "MyEventGridTopicUriSetting", TopicKeySetting = "MyEventGridTopicKeySetting")] public static MyEventType Run([EventGridTrigger] MyEventType input, FunctionContext context) { - var logger = context.GetLogger("EventGridFunction"); + var logger = context.GetLogger(nameof(EventGridFunction)); logger.LogInformation(input.Data.ToString()); diff --git a/samples/Extensions/EventHubs/EventHubsFunction.cs b/samples/Extensions/EventHubs/EventHubsFunction.cs index df204faef..7af123246 100644 --- a/samples/Extensions/EventHubs/EventHubsFunction.cs +++ b/samples/Extensions/EventHubs/EventHubsFunction.cs @@ -2,25 +2,78 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using Azure.Messaging.EventHubs; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; namespace SampleApp { - public static class EventHubsFunction + public class EventHubsFunction { - [Function("EventHubsFunction")] + private readonly ILogger _logger; + + public EventHubsFunction(ILogger logger) + { + _logger = logger; + } + + [Function(nameof(EventHubFunction))] [FixedDelayRetry(5, "00:00:10")] - [EventHubOutput("dest", Connection = "EventHubConnectionAppSetting")] - public static string Run([EventHubTrigger("src", Connection = "EventHubConnectionAppSetting")] string[] input, + [EventHubOutput("dest", Connection = "EventHubConnection")] + public string EventHubFunction( + [EventHubTrigger("src", Connection = "EventHubConnection")] string[] input, FunctionContext context) { - var logger = context.GetLogger("EventHubsFunction"); - - logger.LogInformation($"First Event Hubs triggered message: {input[0]}"); + _logger.LogInformation("First Event Hubs triggered message: {msg}", input[0]); var message = $"Output message created at {DateTime.Now}"; return message; } + + /// + /// This function demonstrates binding to a single . Note that when doing so, you must also set the + /// property to false as the default value of this property is + /// true. + /// + [Function(nameof(EventDataFunctions))] + public void EventDataFunctions( + [EventHubTrigger("queue", Connection = "EventHubConnection", IsBatched = false)] EventData @event) + { + _logger.LogInformation("Event Body: {body}", @event.Body); + _logger.LogInformation("Event Content-Type: {contentType}", @event.ContentType); + } + + /// + /// This function demonstrates binding to an array of . + /// + [Function(nameof(EventDataBatchFunction))] + public void EventDataBatchFunction( + [EventHubTrigger("queue", Connection = "EventHubConnection")] EventData[] events) + { + foreach (EventData @event in events) + { + _logger.LogInformation("Event Body: {body}", @event.Body); + _logger.LogInformation("Event Content-Type: {contentType}", @event.ContentType); + } + } + + /// + /// This functions demonstrates that it is possible to bind to both the and any of the supported binding contract + /// properties at the same time. If attempting this, the must be the first parameter. There is not + /// much benefit to doing this as all of the binding contract properties are available as properties on the . + /// + [Function(nameof(EventDataWithStringPropertiesFunction))] + public void EventDataWithStringPropertiesFunction( + [EventHubTrigger("queue", Connection = "EventHubConnection", IsBatched = false)] + EventData @event, string contentType, long offset) + { + // The ContentType property and the contentType parameter are the same. + _logger.LogInformation("Event Content-Type: {contentType}", @event.ContentType); + _logger.LogInformation("Event Content-Type: {contentType}", contentType); + + // Similarly the Offset property and the offset parameter are the same. + _logger.LogInformation("Event offset: {offset}", @event.Offset); + _logger.LogInformation("Event offset: {offset}", offset); + } } } diff --git a/samples/Extensions/EventHubs/EventHubsTriggerMetadata.cs b/samples/Extensions/EventHubs/EventHubsTriggerMetadata.cs index 18d054aaa..dd5b99534 100644 --- a/samples/Extensions/EventHubs/EventHubsTriggerMetadata.cs +++ b/samples/Extensions/EventHubs/EventHubsTriggerMetadata.cs @@ -10,9 +10,9 @@ namespace SampleApp { public static class EventHubsTriggerMetadata { - [Function("EventHubsTriggerMetadata-Context")] + [Function(nameof(EventHubsTriggerUsingContext))] [FixedDelayRetry(5, "00:00:10")] - public static void UsingContext([EventHubTrigger("src-context", Connection = "EventHubConnectionAppSetting")] string[] messages, FunctionContext context) + public static void EventHubsTriggerUsingContext([EventHubTrigger("src-context", Connection = "EventHubConnection")] string[] messages, FunctionContext context) { // Properties for messages are passed as binding data, which is accessible via the FunctionContext. // However, this requires converting manually into the correct types. @@ -23,9 +23,9 @@ public static void UsingContext([EventHubTrigger("src-context", Connection = "Ev var systemPropertiesArray = context.BindingContext.BindingData["systemPropertiesArray"]; } - [Function("EventHubsTriggerMetadata-Parameters")] + [Function(nameof(EventHubsTriggerUsingParameters))] [FixedDelayRetry(5, "00:00:10")] - public static void UsingParameters([EventHubTrigger("src-parameters", Connection = "EventHubConnectionAppSetting")] string[] messages, + public static void EventHubsTriggerUsingParameters([EventHubTrigger("src-parameters", Connection = "EventHubConnection")] string[] messages, DateTime[] enqueuedTimeUtcArray, long[] sequenceNumberArray, string[] offsetArray, diff --git a/samples/Extensions/Extensions.csproj b/samples/Extensions/Extensions.csproj index 263113b3a..0d66a0883 100644 --- a/samples/Extensions/Extensions.csproj +++ b/samples/Extensions/Extensions.csproj @@ -6,22 +6,22 @@ <_FunctionsSkipCleanOutput>true - - - - + + + + - - - - - - + + + + + + + - - - + + diff --git a/samples/Extensions/Http/HttpFunction.cs b/samples/Extensions/Http/HttpFunction.cs index 871f594df..8293fd059 100644 --- a/samples/Extensions/Http/HttpFunction.cs +++ b/samples/Extensions/Http/HttpFunction.cs @@ -11,19 +11,17 @@ namespace SampleApp public static class HttpFunction { // - [Function("HttpFunction")] + [Function(nameof(HttpFunction))] public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, FunctionContext executionContext) { // - var logger = executionContext.GetLogger("HttpFunction"); + var logger = executionContext.GetLogger(nameof(HttpFunction)); logger.LogInformation("message logged"); // var response = req.CreateResponse(HttpStatusCode.OK); - response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); - response.WriteString("Welcome to .NET isolated worker !!"); return response; diff --git a/samples/Extensions/Kafka/KafkaFunction.cs b/samples/Extensions/Kafka/KafkaFunction.cs index d98359710..2ec29dec4 100644 --- a/samples/Extensions/Kafka/KafkaFunction.cs +++ b/samples/Extensions/Kafka/KafkaFunction.cs @@ -9,14 +9,14 @@ namespace SampleApp { public static class KafkaFunction { - [Function("KafkaFunction")] + [Function(nameof(KafkaFunction))] [FixedDelayRetry(5, "00:00:10")] [KafkaOutput("LocalBroker", "stringTopicTenPartitions")] public static string Run([KafkaTrigger("LocalBroker", "stringTopicTenPartitions", ConsumerGroup = "$Default", AuthenticationMode = BrokerAuthenticationMode.Plain)] string input, FunctionContext context) { - var logger = context.GetLogger("KafkaFunction"); + var logger = context.GetLogger(nameof(KafkaFunction)); logger.LogInformation(input); diff --git a/samples/Extensions/MultiOutput/MultiOutput.cs b/samples/Extensions/MultiOutput/MultiOutput.cs index 36635f346..d661fe014 100644 --- a/samples/Extensions/MultiOutput/MultiOutput.cs +++ b/samples/Extensions/MultiOutput/MultiOutput.cs @@ -16,7 +16,7 @@ namespace SampleApp // public static class MultiOutput { - [Function("MultiOutput")] + [Function(nameof(MultiOutput))] public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req, FunctionContext context) { diff --git a/samples/Extensions/Program.cs b/samples/Extensions/Program.cs index 25151abe6..1ca2e6a12 100644 --- a/samples/Extensions/Program.cs +++ b/samples/Extensions/Program.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -10,6 +11,10 @@ public class Program { public static void Main() { + #if DEBUG + Console.WriteLine($"Azure Functions .NET Worker (PID: { Environment.ProcessId }) initialized in debug mode."); + #endif + var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(s => diff --git a/samples/Extensions/Queue/QueueFunction.cs b/samples/Extensions/Queue/QueueFunction.cs index 92e2ec62b..b9f821c58 100644 --- a/samples/Extensions/Queue/QueueFunction.cs +++ b/samples/Extensions/Queue/QueueFunction.cs @@ -1,37 +1,67 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using Azure.Storage.Queues.Models; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; namespace SampleApp { - public static class QueueFunction + public class QueueFunction { + private readonly ILogger _logger; + + public QueueFunction(ILogger logger) + { + _logger = logger; + } + // // - [Function("QueueFunction")] + [Function(nameof(QueueFunction))] [QueueOutput("output-queue")] - public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem, FunctionContext context) + public string[] Run([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context) // { // Use a string array to return more than one message. string[] messages = { - $"Book name = {myQueueItem.Name}", - $"Book ID = {myQueueItem.Id}"}; - var logger = context.GetLogger("QueueFunction"); - logger.LogInformation($"{messages[0]},{messages[1]}"); + $"Album name = {myQueueItem.Name}", + $"Album songs = {myQueueItem.Songs.ToString()}"}; + + _logger.LogInformation("{msg1},{msg2}", messages[0], messages[1]); // Queue Output messages return messages; } // + + /// + /// This function demonstrates binding to a single . + /// + [Function(nameof(QueueMessageFunction))] + public void QueueMessageFunction([QueueTrigger("input-queue")] QueueMessage message) + { + _logger.LogInformation(message.MessageText); + } + + /// + /// This function demonstrates binding to a single . + /// + [Function(nameof(QueueBinaryDataFunction))] + public void QueueBinaryDataFunction([QueueTrigger("input-queue")] BinaryData message) + { + _logger.LogInformation(message.ToString()); + } } - public class Book + public class Album { + public string Id { get; set; } + public string Name { get; set; } - public string Id { get; set; } + public List Songs { get; set; } } } diff --git a/samples/Extensions/RabbitMQ/RabbitMQFunction.cs b/samples/Extensions/RabbitMQ/RabbitMQFunction.cs index b84cdc39b..16ed16964 100644 --- a/samples/Extensions/RabbitMQ/RabbitMQFunction.cs +++ b/samples/Extensions/RabbitMQ/RabbitMQFunction.cs @@ -9,12 +9,12 @@ namespace SampleApp { public static class RabbitMQFunction { - [Function("RabbitMQFunction")] - [RabbitMQOutput(QueueName = "destinationQueue", ConnectionStringSetting = "rabbitMQConnectionAppSetting")] - public static string Run([RabbitMQTrigger("queue", ConnectionStringSetting = "rabbitMQConnectionAppSetting")] string item, + [Function(nameof(RabbitMQFunction))] + [RabbitMQOutput(QueueName = "destinationQueue", ConnectionStringSetting = "RabbitMQConnection")] + public static string Run([RabbitMQTrigger("queue", ConnectionStringSetting = "RabbitMQConnection")] string item, FunctionContext context) { - var logger = context.GetLogger("RabbitMQFunction"); + var logger = context.GetLogger(nameof(RabbitMQFunction)); logger.LogInformation(item); diff --git a/samples/Extensions/ServiceBus/ServiceBusFunction.cs b/samples/Extensions/ServiceBus/ServiceBusFunction.cs deleted file mode 100644 index 6cd003d66..000000000 --- a/samples/Extensions/ServiceBus/ServiceBusFunction.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.Logging; - -namespace SampleApp -{ - public static class ServiceBusFunction - { - [Function("ServiceBusFunction")] - [ServiceBusOutput("outputQueue", Connection = "ServiceBusConnection")] - public static string Run([ServiceBusTrigger("queue", Connection = "ServiceBusConnection")] string item, - FunctionContext context) - { - var logger = context.GetLogger("ServiceBusFunction"); - - logger.LogInformation(item); - - var message = $"Output message created at {DateTime.Now}"; - return message; - } - } -} diff --git a/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs b/samples/Extensions/ServiceBus/ServiceBusReceivedMessageFunctions.cs similarity index 79% rename from samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs rename to samples/Extensions/ServiceBus/ServiceBusReceivedMessageFunctions.cs index fd7b19437..838ee63b3 100644 --- a/samples/WorkerBindingSamples/ServiceBus/ServiceBusReceivedMessageBindingSamples.cs +++ b/samples/Extensions/ServiceBus/ServiceBusReceivedMessageFunctions.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using Azure.Messaging.ServiceBus; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; @@ -10,32 +11,39 @@ namespace SampleApp /// /// Samples demonstrating binding to the type. /// - public class ServiceBusReceivedMessageBindingSamples + public class ServiceBusReceivedMessageFunctions { - private readonly ILogger _logger; + // + private readonly ILogger _logger; - public ServiceBusReceivedMessageBindingSamples(ILogger logger) + public ServiceBusReceivedMessageFunctions(ILogger logger) { _logger = logger; } - + // /// /// This function demonstrates binding to a single . /// + // [Function(nameof(ServiceBusReceivedMessageFunction))] - public void ServiceBusReceivedMessageFunction( + [ServiceBusOutput("outputQueue", Connection = "ServiceBusConnection")] + public string ServiceBusReceivedMessageFunction( [ServiceBusTrigger("queue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message) { _logger.LogInformation("Message ID: {id}", message.MessageId); _logger.LogInformation("Message Body: {body}", message.Body); _logger.LogInformation("Message Content-Type: {contentType}", message.ContentType); - } + var outputMessage = $"Output message created at {DateTime.Now}"; + return outputMessage; + } + // /// /// This function demonstrates binding to an array of . /// Note that when doing so, you must also set the property /// to true. /// + // [Function(nameof(ServiceBusReceivedMessageBatchFunction))] public void ServiceBusReceivedMessageBatchFunction( [ServiceBusTrigger("queue", Connection = "ServiceBusConnection", IsBatched = true)] ServiceBusReceivedMessage[] messages) @@ -47,7 +55,7 @@ public void ServiceBusReceivedMessageBatchFunction( _logger.LogInformation("Message Content-Type: {contentType}", message.ContentType); } } - + // /// /// This functions demonstrates that it is possible to bind to both the ServiceBusReceivedMessage and any of the supported binding contract /// properties at the same time. If attempting this, the ServiceBusReceivedMessage must be the first parameter. There is not @@ -67,4 +75,4 @@ public void ServiceBusReceivedMessageWithStringProperties( _logger.LogInformation("Delivery Count: {count}", deliveryCount); } } -} \ No newline at end of file +} diff --git a/samples/Extensions/SignalR/SignalRFunction.cs b/samples/Extensions/SignalR/SignalRFunction.cs index 4e6bb6901..b567e7928 100644 --- a/samples/Extensions/SignalR/SignalRFunction.cs +++ b/samples/Extensions/SignalR/SignalRFunction.cs @@ -9,17 +9,17 @@ namespace SampleApp { public static class SignalRFunction { - [Function("SignalRFunction")] - [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnectionString")] + [Function(nameof(SignalRFunction))] + [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static MyMessage Run([SignalRTrigger("SignalRTest", "messages", "SendMessage", parameterNames: new string[] { "message" }, - ConnectionStringSetting = "SignalRConnectionString")] string item, + ConnectionStringSetting = "SignalRConnection")] string item, [SignalRConnectionInfoInput(HubName = "chat")] MyConnectionInfo connectionInfo, FunctionContext context) { - var logger = context.GetLogger("SignalRFunction"); + var logger = context.GetLogger(nameof(SignalRFunction)); logger.LogInformation(item); - logger.LogInformation($"Connection URL = {connectionInfo.Url}"); + logger.LogInformation("Connection URL = {url}", connectionInfo.Url); var message = $"Output message created at {DateTime.Now}"; diff --git a/samples/Extensions/SignalR/SignalRNegotiationFunctions.cs b/samples/Extensions/SignalR/SignalRNegotiationFunctions.cs index 4059455cf..77e6d200d 100644 --- a/samples/Extensions/SignalR/SignalRNegotiationFunctions.cs +++ b/samples/Extensions/SignalR/SignalRNegotiationFunctions.cs @@ -10,7 +10,7 @@ namespace Extensions.SignalR public static class SignalRNegotiationFunctions { // - [Function("Negotiate")] + [Function(nameof(Negotiate))] public static string Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req, [SignalRConnectionInfoInput(HubName = "serverless")] string connectionInfo) { @@ -26,7 +26,7 @@ public static string Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpR { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - [Function("NegotiateWithMultipleEndpoints")] + [Function(nameof(NegotiateWithMultipleEndpoints))] public static string NegotiateWithMultipleEndpoints( [HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req, [SignalRNegotiationInput("chatHub", "SignalRConnection")] SignalRNegotiationContext negotiationContext) diff --git a/samples/Extensions/SignalR/SignalROutputBindingFunctions.cs b/samples/Extensions/SignalR/SignalROutputBindingFunctions.cs index 553925a9b..b851f7d52 100644 --- a/samples/Extensions/SignalR/SignalROutputBindingFunctions.cs +++ b/samples/Extensions/SignalR/SignalROutputBindingFunctions.cs @@ -10,7 +10,7 @@ namespace SampleApp { public static class SignalROutputBindingFunctions { - [Function("BroadcastToAll")] + [Function(nameof(BroadcastToAll))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction BroadcastToAll( [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) @@ -23,7 +23,7 @@ public static SignalRMessageAction BroadcastToAll( }; } - [Function("SendToConnection")] + [Function(nameof(SendToConnection))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToConnection( [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) @@ -36,7 +36,7 @@ public static SignalRMessageAction SendToConnection( }; } - [Function("SendToUser")] + [Function(nameof(SendToUser))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToUser([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { @@ -48,7 +48,7 @@ public static SignalRMessageAction SendToUser([HttpTrigger(AuthorizationLevel.An }; } - [Function("SendToGroup")] + [Function(nameof(SendToGroup))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { @@ -60,7 +60,7 @@ public static SignalRMessageAction SendToGroup([HttpTrigger(AuthorizationLevel.A }; } - [Function("SendToEndpoint")] + [Function(nameof(SendToEndpoint))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToEndpoint( [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req, @@ -76,7 +76,7 @@ public static SignalRMessageAction SendToEndpoint( }; } - [Function("RemoveFromGroup")] + [Function(nameof(RemoveFromGroup))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRGroupAction RemoveFromGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { diff --git a/samples/Extensions/SignalR/SignalROutputBindingFunctions2.cs b/samples/Extensions/SignalR/SignalROutputBindingFunctions2.cs index 25885e900..72e25b544 100644 --- a/samples/Extensions/SignalR/SignalROutputBindingFunctions2.cs +++ b/samples/Extensions/SignalR/SignalROutputBindingFunctions2.cs @@ -14,7 +14,7 @@ namespace SampleApp public static class SignalROutputBindingFunctions2 { // - [Function("BroadcastToAll")] + [Function(nameof(BroadcastToAll))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction BroadcastToAll([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { @@ -28,7 +28,7 @@ public static SignalRMessageAction BroadcastToAll([HttpTrigger(AuthorizationLeve // // - [Function("SendToConnection")] + [Function(nameof(SendToConnection))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToConnection([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { @@ -42,7 +42,7 @@ public static SignalRMessageAction SendToConnection([HttpTrigger(AuthorizationLe // // - [Function("SendToUser")] + [Function(nameof(SendToUser))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToUser([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { @@ -56,7 +56,7 @@ public static SignalRMessageAction SendToUser([HttpTrigger(AuthorizationLevel.An // // - [Function("SendToGroup")] + [Function(nameof(SendToGroup))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { @@ -70,7 +70,7 @@ public static SignalRMessageAction SendToGroup([HttpTrigger(AuthorizationLevel.A // // - [Function("SendToEndpoint")] + [Function(nameof(SendToEndpoint))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRMessageAction SendToEndpoint( [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req, @@ -88,7 +88,7 @@ public static SignalRMessageAction SendToEndpoint( // // - [Function("RemoveFromGroup")] + [Function(nameof(RemoveFromGroup))] [SignalROutput(HubName = "chat", ConnectionStringSetting = "SignalRConnection")] public static SignalRGroupAction RemoveFromGroup([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req) { diff --git a/samples/Extensions/SignalR/SignalRTriggerFunctions.cs b/samples/Extensions/SignalR/SignalRTriggerFunctions.cs index 1206200a5..d46a0f452 100644 --- a/samples/Extensions/SignalR/SignalRTriggerFunctions.cs +++ b/samples/Extensions/SignalR/SignalRTriggerFunctions.cs @@ -10,29 +10,35 @@ namespace Extensions.SignalR public static class SignalRTriggerFunctions { // - [Function("OnConnected")] - public static void OnConnected([SignalRTrigger("chat", "connections", "connected", ConnectionStringSetting = "SignalRConnection")] SignalRInvocationContext invocationContext, FunctionContext functionContext) + [Function(nameof(OnConnected))] + public static void OnConnected( + [SignalRTrigger("chat", "connections", "connected", ConnectionStringSetting = "SignalRConnection")] + SignalRInvocationContext invocationContext, FunctionContext functionContext) { - var logger = functionContext.GetLogger("OnConnected"); - logger.LogInformation($"Connection {invocationContext.ConnectionId} is connected"); + var logger = functionContext.GetLogger(nameof(OnConnected)); + logger.LogInformation("Connection {connectionId} is connected", invocationContext.ConnectionId); } // // - [Function("OnDisconnected")] - public static void OnDisconnected([SignalRTrigger("chat", "connections", "disconnected", ConnectionStringSetting = "SignalRConnection")] SignalRInvocationContext invocationContext, FunctionContext functionContext) + [Function(nameof(OnDisconnected))] + public static void OnDisconnected( + [SignalRTrigger("chat", "connections", "disconnected", ConnectionStringSetting = "SignalRConnection")] + SignalRInvocationContext invocationContext, FunctionContext functionContext) { - var logger = functionContext.GetLogger("OnDisconnected"); - logger.LogInformation($"Connection {invocationContext.ConnectionId} is disconnected. Error: {invocationContext.Error}"); + var logger = functionContext.GetLogger(nameof(OnDisconnected)); + logger.LogInformation("Connection {connectionId} is disconnected. Error: {error}", invocationContext.ConnectionId, invocationContext.Error); } // // - [Function("OnClientMessage")] - public static void OnClientMessage([SignalRTrigger("Hub", "messages", "sendMessage", "content", ConnectionStringSetting = "SignalRConnection")] SignalRInvocationContext invocationContext, string content, FunctionContext functionContext) + [Function(nameof(OnClientMessage))] + public static void OnClientMessage( + [SignalRTrigger("Hub", "messages", "sendMessage", "content", ConnectionStringSetting = "SignalRConnection")] + SignalRInvocationContext invocationContext, string content, FunctionContext functionContext) { - var logger = functionContext.GetLogger("OnClientMessage"); - logger.LogInformation($"Connection {invocationContext.ConnectionId} sent a message. Message content: {content}"); + var logger = functionContext.GetLogger(nameof(OnClientMessage)); + logger.LogInformation("Connection {connectionId} sent a message. Message content: {content}", invocationContext.ConnectionId, content); } // } diff --git a/samples/Extensions/Table/TableFunction.cs b/samples/Extensions/Table/TableFunction.cs index d755a8d15..95894ed68 100644 --- a/samples/Extensions/Table/TableFunction.cs +++ b/samples/Extensions/Table/TableFunction.cs @@ -2,30 +2,101 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Azure.Data.Tables; using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace SampleApp { - public static class TableFunction + public class TableFunction { - [Function("TableFunction")] - [TableOutput("OutputTable", Connection = "AzureWebJobsStorage")] - public static MyTableData Run( - [QueueTrigger("table-items")] string input, - [TableInput("MyTable", "", "{queueTrigger}")] MyTableData tableInput, - FunctionContext context) + private readonly ILogger _logger; + + public TableFunction(ILogger logger) + { + _logger = logger; + } + + /// + /// This function demonstrates binding to a single + /// and using the client for output. + /// + [Function(nameof(TableFunction))] + public async Task Run( + [QueueTrigger("table-items")] string rowKey, + [TableInput("MyTable")] TableClient table) + { + var tableEntities = table.QueryAsync(filter: $"RowKey eq '{rowKey}'"); + + await foreach (TableEntity entity in tableEntities) + { + _logger.LogInformation("PK={pk}, RK={rk}, Text={t}", entity.PartitionKey, entity.RowKey, entity["Text"]); + } + + await table.AddEntityAsync(new TableEntity("processed", Guid.NewGuid().ToString()) + { + ["Text"] = $"Record with row key {rowKey} processed at {DateTime.Now}" + }); + } + + /// + /// This function demonstrates binding to a single , using the + /// and properties. + /// + [Function(nameof(TableEntityFunction))] + public void TableEntityFunction( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "items/{partitionKey}/{rowKey}")] HttpRequestData req, + [TableInput("MyTable", "{partitionKey}", "{rowKey}")] TableEntity entity) { - var logger = context.GetLogger("TableFunction"); + _logger.LogInformation("PK={pk}, RK={rk}, Text={t}", entity.PartitionKey, entity.RowKey, entity["Text"]); + } + + /// + /// This function demonstrates binding to a collection of , using the . + /// Note that when the is not provided, you are able to bind to a collection. + /// + [Function(nameof(TableEntityCollectionFunction))] + public void TableEntityCollectionFunction( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "items/{partitionKey}")] HttpRequestData req, + [TableInput("MyTable", "{partitionKey}")] IEnumerable entities) + { + foreach (var entity in entities) + { + _logger.LogInformation("PK={pk}, RK={rk}, Text={t}", entity.PartitionKey, entity.RowKey, entity["Text"]); + } + } - logger.LogInformation($"PK={tableInput.PartitionKey}, RK={tableInput.RowKey}, Text={tableInput.Text}"); + /// + /// This function demonstrates binding to a collection of , using + /// to filter on the row key. This sample also demonstrates using + /// to limit the number of entities returned. + /// + [Function(nameof(TableEntityWithFilterFunction))] + public void TableEntityWithFilterFunction( + [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, + [TableInput("MyTable", "PartitionKey", 2, Filter = "RowKey ne 'value'")] IEnumerable entities) + { + foreach (var entity in entities) + { + _logger.LogInformation("PK={pk}, RK={rk}, Text={t}", entity.PartitionKey, entity.RowKey, entity["Text"]); + } + } - return new MyTableData() + /// + /// This function demonstrates binding to a collection of + /// + [Function(nameof(TablePocoFunction))] + public void TablePocoFunction( + [HttpTrigger(AuthorizationLevel.Function, "get","post", Route = null)] HttpRequestData req, + [TableInput("MyTable")] IEnumerable entities) + { + foreach (var entity in entities) { - PartitionKey = "queue", - RowKey = Guid.NewGuid().ToString(), - Text = $"Output record with rowkey {input} created at {DateTime.Now}" - }; + _logger.LogInformation("PK={pk}, RK={rk}, Text={t}", entity.PartitionKey, entity.RowKey, entity.Text); + } } } diff --git a/samples/Extensions/Timer/TimerFunction.cs b/samples/Extensions/Timer/TimerFunction.cs index 2ccc8db33..e94913097 100644 --- a/samples/Extensions/Timer/TimerFunction.cs +++ b/samples/Extensions/Timer/TimerFunction.cs @@ -9,12 +9,12 @@ namespace SampleApp public static class TimerFunction { // - [Function("TimerFunction")] + [Function(nameof(TimerFunction))] [FixedDelayRetry(5, "00:00:10")] public static void Run([TimerTrigger("0 */5 * * * *")] TimerInfo timerInfo, FunctionContext context) { - var logger = context.GetLogger("TimerFunction"); + var logger = context.GetLogger(nameof(TimerFunction)); logger.LogInformation($"Function Ran. Next timer schedule = {timerInfo.ScheduleStatus.Next}"); } // diff --git a/samples/Extensions/Warmup/Warmup.cs b/samples/Extensions/Warmup/Warmup.cs index e84634e94..056daee34 100644 --- a/samples/Extensions/Warmup/Warmup.cs +++ b/samples/Extensions/Warmup/Warmup.cs @@ -8,11 +8,10 @@ namespace SampleApp { public static class Warmup { - [Function("Warmup")] + [Function(nameof(Warmup))] public static void Run([WarmupTrigger] object warmupContext, FunctionContext context) { - var logger = context.GetLogger("Warmup"); - + var logger = context.GetLogger(nameof(Warmup)); logger.LogInformation("Function App instance is now warm!"); } } diff --git a/samples/Extensions/local.settings.json b/samples/Extensions/local.settings.json index a6f19361a..3a4a9217a 100644 --- a/samples/Extensions/local.settings.json +++ b/samples/Extensions/local.settings.json @@ -4,9 +4,12 @@ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", "AzureWebJobsStorage": "", "CosmosConnection": "", - "SignalRConnection": "", "CosmosDb": "ItemDb", - "CosmosCollIn": "ItemCollectionIn", - "CosmosCollOut": "ItemCollectionOut" + "CosmosContainerIn": "ItemContainerIn", + "CosmosContainerOut": "ItemContainerOut", + "EventHubConnection": "", + "RabbitMQConnection": "", + "ServiceBusConnection": "", + "SignalRConnection": "" } } diff --git a/samples/FunctionApp/FunctionApp.csproj b/samples/FunctionApp/FunctionApp.csproj index 1d76510fe..1c378e023 100644 --- a/samples/FunctionApp/FunctionApp.csproj +++ b/samples/FunctionApp/FunctionApp.csproj @@ -14,7 +14,7 @@ - + @@ -47,4 +47,4 @@ https://github.com/JoanComasFdz/dotnet-how-to-debug-source-generator-vs2022/blob --> - \ No newline at end of file + diff --git a/samples/WorkerBindingSamples/Cosmos/CosmosTriggerFunction.cs b/samples/WorkerBindingSamples/Cosmos/CosmosTriggerFunction.cs deleted file mode 100644 index d43086c9d..000000000 --- a/samples/WorkerBindingSamples/Cosmos/CosmosTriggerFunction.cs +++ /dev/null @@ -1,42 +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; -using Microsoft.Extensions.Logging; - -namespace SampleApp -{ - /// - /// Samples demonstrating binding to the type. - /// - public class CosmosTriggerFunction - { - private readonly ILogger _logger; - - public CosmosTriggerFunction(ILogger logger) - { - _logger = logger; - } - - /// - /// This function demonstrates binding to a collection of . - /// - [Function(nameof(CosmosTriggerFunction))] - public void Run([CosmosDBTrigger( - databaseName: "ToDoItems", - containerName:"TriggerItems", - Connection = "CosmosDBConnection", - LeaseContainerName = "leases", - CreateLeaseContainerIfNotExists = true)] IReadOnlyList todoItems, - FunctionContext context) - { - if (todoItems is not null && todoItems.Any()) - { - foreach (var doc in todoItems) - { - _logger.LogInformation("ToDoItem: {desc}", doc.Description); - } - } - } - } -} diff --git a/samples/WorkerBindingSamples/Cosmos/ToDoItem.cs b/samples/WorkerBindingSamples/Cosmos/ToDoItem.cs deleted file mode 100644 index 86027c8f2..000000000 --- a/samples/WorkerBindingSamples/Cosmos/ToDoItem.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace SampleApp -{ - public class ToDoItem - { - public string? Id { get; set; } - public string? Description { get; set; } - } -} diff --git a/samples/WorkerBindingSamples/EventHubs/EventDataSamples.cs b/samples/WorkerBindingSamples/EventHubs/EventDataSamples.cs deleted file mode 100644 index 5ef7192ca..000000000 --- a/samples/WorkerBindingSamples/EventHubs/EventDataSamples.cs +++ /dev/null @@ -1,68 +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 Azure.Messaging.EventHubs; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.Logging; - -namespace SampleApp -{ - /// - /// Samples demonstrating binding to the type. - /// - public class EventDataSamples - { - private readonly ILogger _logger; - - public EventDataSamples(ILogger logger) - { - _logger = logger; - } - - /// - /// This function demonstrates binding to a single . Note that when doing so, you must also set the - /// property to false as the default value of this property is - /// true. - /// - [Function(nameof(EventDataFunctions))] - public void EventDataFunctions( - [EventHubTrigger("queue", Connection = "EventHubConnection", IsBatched = false)] EventData @event) - { - _logger.LogInformation("Event Body: {body}", @event.Body); - _logger.LogInformation("Event Content-Type: {contentType}", @event.ContentType); - } - - /// - /// This function demonstrates binding to an array of . - /// - [Function(nameof(EventDataBatchFunction))] - public void EventDataBatchFunction( - [EventHubTrigger("queue", Connection = "EventHubConnection")] EventData[] events) - { - foreach (EventData @event in events) - { - _logger.LogInformation("Event Body: {body}", @event.Body); - _logger.LogInformation("Event Content-Type: {contentType}", @event.ContentType); - } - } - - /// - /// This functions demonstrates that it is possible to bind to both the and any of the supported binding contract - /// properties at the same time. If attempting this, the must be the first parameter. There is not - /// much benefit to doing this as all of the binding contract properties are available as properties on the . - /// - [Function(nameof(EventDataWithStringPropertiesFunction))] - public void EventDataWithStringPropertiesFunction( - [EventHubTrigger("queue", Connection = "EventHubConnection", IsBatched = false)] - EventData @event, string contentType, long offset) - { - // The ContentType property and the contentType parameter are the same. - _logger.LogInformation("Event Content-Type: {contentType}", @event.ContentType); - _logger.LogInformation("Event Content-Type: {contentType}", contentType); - - // Similarly the Offset property and the offset parameter are the same. - _logger.LogInformation("Event offset: {offset}", @event.Offset); - _logger.LogInformation("Event offset: {offset}", offset); - } - } -} \ No newline at end of file diff --git a/samples/WorkerBindingSamples/Program.cs b/samples/WorkerBindingSamples/Program.cs deleted file mode 100644 index e0ff09a1b..000000000 --- a/samples/WorkerBindingSamples/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using Microsoft.Extensions.Hosting; - -namespace SampleApp -{ - public class Program - { - public static void Main() - { - #if DEBUG - Console.WriteLine($"Azure Functions .NET Worker (PID: { Environment.ProcessId }) initialized in debug mode."); - #endif - - var host = new HostBuilder() - .ConfigureFunctionsWorkerDefaults() - .Build(); - - host.Run(); - } - } -} diff --git a/samples/WorkerBindingSamples/Queue/QueueSamples.cs b/samples/WorkerBindingSamples/Queue/QueueSamples.cs deleted file mode 100644 index c1240c8b3..000000000 --- a/samples/WorkerBindingSamples/Queue/QueueSamples.cs +++ /dev/null @@ -1,40 +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 Azure.Storage.Queues.Models; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.Logging; - -namespace SampleApp -{ - /// - /// Samples demonstrating binding to and types. - /// - public class QueueSamples - { - private readonly ILogger _logger; - - public QueueSamples(ILogger logger) - { - _logger = logger; - } - - /// - /// This function demonstrates binding to a single . - /// - [Function(nameof(QueueMessageFunction))] - public void QueueMessageFunction([QueueTrigger("input-queue")] QueueMessage message) - { - _logger.LogInformation(message.MessageText); - } - - /// - /// This function demonstrates binding to a single . - /// - [Function(nameof(QueueBinaryDataFunction))] - public void QueueBinaryDataFunction([QueueTrigger("input-queue-binarydata")] BinaryData message) - { - _logger.LogInformation(message.ToString()); - } - } -} \ No newline at end of file diff --git a/samples/WorkerBindingSamples/README.md b/samples/WorkerBindingSamples/README.md deleted file mode 100644 index 4c5072518..000000000 --- a/samples/WorkerBindingSamples/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Worker Binding Samples - -This sample project provides simple examples of how to use various bindings supported by the .NET Worker. diff --git a/samples/WorkerBindingSamples/WorkerBindingSamples.csproj b/samples/WorkerBindingSamples/WorkerBindingSamples.csproj deleted file mode 100644 index 0b8dd6396..000000000 --- a/samples/WorkerBindingSamples/WorkerBindingSamples.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - net7.0 - v4 - Exe - enable - enable - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - \ No newline at end of file diff --git a/samples/WorkerBindingSamples/host.json b/samples/WorkerBindingSamples/host.json deleted file mode 100644 index beb2e4020..000000000 --- a/samples/WorkerBindingSamples/host.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - } -} \ No newline at end of file diff --git a/samples/WorkerBindingSamples/local.settings.json b/samples/WorkerBindingSamples/local.settings.json deleted file mode 100644 index 7a87bbf28..000000000 --- a/samples/WorkerBindingSamples/local.settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "CosmosDBConnection": "", - "EventHubConnection": "", - "ServiceBusConnection": "" - } -} \ No newline at end of file diff --git a/sdk/Sdk.Generators/Constants.cs b/sdk/Sdk.Generators/Constants.cs index 95ad3ddea..c64b01633 100644 --- a/sdk/Sdk.Generators/Constants.cs +++ b/sdk/Sdk.Generators/Constants.cs @@ -12,6 +12,8 @@ internal static class Languages internal static class BuildProperties { + internal const string MSBuildRootNamespace = "build_property.RootNamespace"; + internal const string GeneratedCodeNamespace = "build_property.FunctionsGeneratedCodeNamespace"; internal const string EnableSourceGen = "build_property.FunctionsEnableMetadataSourceGen"; internal const string EnablePlaceholder = "build_property.FunctionsEnableExecutorSourceGen"; internal const string AutoRegisterGeneratedFunctionsExecutor = "build_property.FunctionsAutoRegisterGeneratedFunctionsExecutor"; diff --git a/sdk/Sdk.Generators/DiagnosticDescriptors.cs b/sdk/Sdk.Generators/DiagnosticDescriptors.cs index 2ebf9408a..9aaca2a6b 100644 --- a/sdk/Sdk.Generators/DiagnosticDescriptors.cs +++ b/sdk/Sdk.Generators/DiagnosticDescriptors.cs @@ -62,5 +62,12 @@ private static DiagnosticDescriptor Create(string id, string title, string messa messageFormat: "Invalid use of a retry attribute. Check that the attribute is used on a trigger that supports function-level retry.", category: "FunctionMetadataGeneration", severity: DiagnosticSeverity.Error); + public static DiagnosticDescriptor InvalidBindingAttributeArgument { get; } + = Create(id: "AZFW0013", + title: "Invalid argument in binding attribute.", + messageFormat: "Invalid argument passed in binding attribute. Check that the argument is not null.", + category: "FunctionMetadataGeneration", + severity: DiagnosticSeverity.Error); + } } diff --git a/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs b/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs index 48b20d456..48754e943 100644 --- a/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs +++ b/sdk/Sdk.Generators/ExtensionStartupRunnerGenerator.cs @@ -69,7 +69,7 @@ public void Execute(GeneratorExecutionContext context) return; } - var source = GenerateExtensionStartupRunner(extensionStartupTypeNames); + var source = GenerateExtensionStartupRunner(context, extensionStartupTypeNames); var sourceText = SourceText.From(source, encoding: Encoding.UTF8); // Add the source code to the compilation @@ -81,18 +81,20 @@ public void Execute(GeneratorExecutionContext context) /// /// The types to add to the configuration/bootstrapping process. /// The generated source code. - internal string GenerateExtensionStartupRunner(IList extensionStartupTypeNames) + internal string GenerateExtensionStartupRunner(GeneratorExecutionContext context, IList extensionStartupTypeNames) { string startupCodeExecutor = GenerateStartupCodeExecutorClass(extensionStartupTypeNames); + var namespaceValue = FunctionsUtil.GetNamespaceForGeneratedCode(context); return $$""" // using System; + using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Core; - [assembly: WorkerExtensionStartupCodeExecutorInfo(typeof(Microsoft.Azure.Functions.Worker.WorkerExtensionStartupCodeExecutor))] + [assembly: WorkerExtensionStartupCodeExecutorInfo(typeof({{namespaceValue}}.WorkerExtensionStartupCodeExecutor))] - namespace Microsoft.Azure.Functions.Worker + namespace {{namespaceValue}} { internal class WorkerExtensionStartupCodeExecutor : WorkerExtensionStartup { diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs index 7dc051d6e..abf0e51bf 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading; +using Microsoft.CodeAnalysis; namespace Microsoft.Azure.Functions.Worker.Sdk.Generators { @@ -13,7 +13,7 @@ public partial class FunctionExecutorGenerator { internal static class Emitter { - internal static string Emit(IEnumerable functions, bool includeAutoRegistrationCode, CancellationToken cancellationToken) + internal static string Emit(GeneratorExecutionContext context, IEnumerable functions, bool includeAutoRegistrationCode) { string result = $$""" @@ -23,9 +23,10 @@ internal static string Emit(IEnumerable functions, bool incl using System.Collections.Generic; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Invocation; - namespace Microsoft.Azure.Functions.Worker + namespace {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}} { internal class DirectFunctionExecutor : IFunctionExecutor { diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs index bce2c2366..ae160a342 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs @@ -32,8 +32,7 @@ internal ICollection GetFunctions(List GetFunctions(List /// The parameter associated with a binding attribute that supports cardinality represented as an . - /// The representation of the paramter's type if it exists. /// The binding attribute that supports cardinality. /// The that best represents the parameter. /// Returns true if the parameter is compatible with the cardinality defined by the attribute, else returns false. - public bool IsCardinalityValid(IParameterSymbol parameterSymbol, TypeSyntax? parameterTypeSyntax, AttributeData attribute, out DataType dataType) + public bool IsCardinalityValid(IParameterSymbol parameterSymbol, AttributeData attribute, out DataType dataType) { dataType = DataType.Undefined; var cardinalityIsNamedArg = false; @@ -139,12 +138,7 @@ public bool IsCardinalityValid(IParameterSymbol parameterSymbol, TypeSyntax? par } else if (isGenericEnumerable) { - if (parameterTypeSyntax is null) - { - return false; - } - - dataType = ResolveIEnumerableOfT(parameterSymbol, parameterTypeSyntax, out bool hasError); + dataType = ResolveIEnumerableOfT(parameterSymbol, out bool hasError); if (hasError) { @@ -164,10 +158,9 @@ public bool IsCardinalityValid(IParameterSymbol parameterSymbol, TypeSyntax? par /// Find the underlying data type of an IEnumerableOfT (String, Binary, Undefined) /// ex. IEnumerable would return DataType.Binary /// - private DataType ResolveIEnumerableOfT(IParameterSymbol parameterSymbol, TypeSyntax parameterSyntax, out bool hasError) + private DataType ResolveIEnumerableOfT(IParameterSymbol parameterSymbol, out bool hasError) { var result = DataType.Undefined; - var currentSyntax = parameterSyntax; hasError = false; var currSymbol = parameterSymbol.Type; diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs index 193a4e870..63016583a 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Emitter.cs @@ -5,6 +5,7 @@ using System.Text; using System.Text.Json; using System.Threading; +using Microsoft.CodeAnalysis; namespace Microsoft.Azure.Functions.Worker.Sdk.Generators { @@ -18,9 +19,9 @@ internal sealed class Emitter DictionaryKeyPolicy = JsonNamingPolicy.CamelCase }; - public string Emit(IReadOnlyList funcMetadata, bool includeAutoRegistrationCode, CancellationToken cancellationToken) + public string Emit(GeneratorExecutionContext context, IReadOnlyList funcMetadata, bool includeAutoRegistrationCode) { - string functionMetadataInfo = AddFunctionMetadataInfo(funcMetadata, cancellationToken); + string functionMetadataInfo = AddFunctionMetadataInfo(funcMetadata, context.CancellationToken); return $$""" // @@ -29,11 +30,12 @@ public string Emit(IReadOnlyList funcMetadata, bool i 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 Microsoft.Azure.Functions.Worker + namespace {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}} { public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider { diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index 2e6cf4ecc..5d4ce348a 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -42,34 +42,33 @@ public Parser(GeneratorExecutionContext context) /// Takes in candidate methods from the user compilation and parses them to return function metadata info as GeneratorFunctionMetadata. /// /// List of candidate methods from the syntax receiver. - public IReadOnlyList GetFunctionMetadataInfo(List methods) + public IReadOnlyList GetFunctionMetadataInfo(List methods) { var result = ImmutableArray.CreateBuilder(); - var assemblyName = Compilation.Assembly.Name; - var scriptFile = Path.Combine(assemblyName + ".dll"); - // Loop through the candidate methods (methods with any attribute associated with them) - foreach (MethodDeclarationSyntax method in methods) + foreach (IMethodSymbol method in methods) { CancellationToken.ThrowIfCancellationRequested(); - var model = Compilation.GetSemanticModel(method.SyntaxTree); - - if (!FunctionsUtil.IsValidFunctionMethod(_context, Compilation, model, method, out string? functionName)) + string? funcName = null; + if (!FunctionsUtil.TryGetFunctionName(method, Compilation, out funcName)) { - continue; + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, Location.None, method.Name)); // would only reach here if the function attribute or method was not loaded, resulting in failure to retrieve name } + var assemblyName = method.ContainingAssembly.Name; + var scriptFile = Path.Combine(assemblyName + ".dll"); + var newFunction = new GeneratorFunctionMetadata { - Name = functionName, - EntryPoint = FunctionsUtil.GetFullyQualifiedMethodName(method, model), + Name = funcName, + EntryPoint = FunctionsUtil.GetFullyQualifiedMethodName(method), Language = Constants.Languages.DotnetIsolated, ScriptFile = scriptFile }; - if (!TryGetBindings(method, model, out IList>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? retryOptions)) + if (!TryGetBindings(method, out IList>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? retryOptions)) { continue; } @@ -92,14 +91,14 @@ public IReadOnlyList GetFunctionMetadataInfo(List>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? validatedRetryOptions) + private bool TryGetBindings(IMethodSymbol method, out IList>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? validatedRetryOptions) { hasHttpTrigger = false; validatedRetryOptions = null; - if (!TryGetMethodOutputBinding(method, model, out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? methodOutputBindings) - || !TryGetParameterInputAndTriggerBindings(method, model, out bool supportsRetryOptions, out hasHttpTrigger, out IList>? parameterInputAndTriggerBindings) - || !TryGetReturnTypeBindings(method, model, hasHttpTrigger, hasOutputBinding, out IList>? returnTypeBindings)) + if (!TryGetMethodOutputBinding(method, out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? methodOutputBindings) + || !TryGetParameterInputAndTriggerBindings(method, out bool supportsRetryOptions, out hasHttpTrigger, out IList>? parameterInputAndTriggerBindings) + || !TryGetReturnTypeBindings(method, hasHttpTrigger, hasOutputBinding, out IList>? returnTypeBindings)) { bindings = null; return false; @@ -121,7 +120,7 @@ private bool TryGetBindings(MethodDeclarationSyntax method, SemanticModel model, } else if (!supportsRetryOptions) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidRetryOptions, method.GetLocation())); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidRetryOptions, Location.None)); return false; } } @@ -132,12 +131,9 @@ private bool TryGetBindings(MethodDeclarationSyntax method, SemanticModel model, /// /// Checks for and returns any OutputBinding attributes associated with the method. /// - private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticModel model, out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? bindingsList) + private bool TryGetMethodOutputBinding(IMethodSymbol method,out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? bindingsList) { - var bindingLocation = method.Identifier.GetLocation(); - - var methodSymbol = model.GetDeclaredSymbol(method); - var attributes = methodSymbol!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created + var attributes = method!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created AttributeData? outputBindingAttribute = null; hasOutputBinding = false; @@ -147,7 +143,7 @@ private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticM { if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass?.BaseType, _knownFunctionMetadataTypes.RetryAttribute)) { - if (TryGetRetryOptionsFromAttribute(attribute, method.GetLocation(), out GeneratorRetryOptions? retryOptionsFromAttr)) + if (TryGetRetryOptionsFromAttribute(attribute, Location.None, out GeneratorRetryOptions? retryOptionsFromAttr)) { retryOptions = retryOptionsFromAttr; } @@ -158,7 +154,7 @@ private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticM // There can only be one output binding associated with a function. If there is more than one, we return a diagnostic error here. if (hasOutputBinding) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, bindingLocation, new object[] { "Method", method.Identifier.ToString() })); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, Location.None, new object[] { "Method", method.Name })); bindingsList = null; return false; } @@ -170,7 +166,7 @@ private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticM if (outputBindingAttribute != null) { - if (!TryCreateBindingDict(outputBindingAttribute, Constants.FunctionMetadataBindingProps.ReturnBindingName, bindingLocation, out IDictionary? bindingDict)) + if (!TryCreateBindingDict(outputBindingAttribute, Constants.FunctionMetadataBindingProps.ReturnBindingName, Location.None, out IDictionary? bindingDict)) { bindingsList = null; return false; @@ -244,29 +240,22 @@ private bool TryGetRetryOptionsFromAttribute(AttributeData attribute, Location l /// /// Checks for and returns input and trigger bindings found in the parameters of the Azure Function method. /// - private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax method, SemanticModel model, out bool supportsRetryOptions, out bool hasHttpTrigger, out IList>? bindingsList) + private bool TryGetParameterInputAndTriggerBindings(IMethodSymbol method, out bool supportsRetryOptions, out bool hasHttpTrigger, out IList>? bindingsList) { supportsRetryOptions = false; hasHttpTrigger = false; bindingsList = new List>(); - foreach (ParameterSyntax parameter in method.ParameterList.Parameters) + foreach (IParameterSymbol parameter in method.Parameters) { // If there's no attribute, we can assume that this parameter is not a binding - if (parameter.AttributeLists.Count == 0) + if (!parameter.GetAttributes().Any()) { continue; } - if (model.GetDeclaredSymbol(parameter) is not IParameterSymbol parameterSymbol) - { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, parameter.Identifier.GetLocation(), nameof(parameterSymbol))); - bindingsList = null; - return false; - } - // Check to see if any of the attributes associated with this parameter is a BindingAttribute - foreach (var attribute in parameterSymbol.GetAttributes()) + foreach (var attribute in parameter.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass?.BaseType?.BaseType, _knownFunctionMetadataTypes.BindingAttribute)) { @@ -276,23 +265,18 @@ private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax meth hasHttpTrigger = true; } - DataType dataType = _dataTypeParser.GetDataType(parameterSymbol.Type); + DataType dataType = _dataTypeParser.GetDataType(parameter.Type); bool cardinalityValidated = false; - bool supportsDeferredBinding = false; - - if (SupportsDeferredBinding(attribute, parameterSymbol.Type.ToString())) - { - supportsDeferredBinding = true; - } + bool supportsDeferredBinding = SupportsDeferredBinding(attribute, parameter.Type.ToString()); if (_cardinalityParser.IsCardinalitySupported(attribute)) { DataType updatedDataType = DataType.Undefined; - if (!_cardinalityParser.IsCardinalityValid(parameterSymbol, parameter.Type, attribute, out updatedDataType)) + if (!_cardinalityParser.IsCardinalityValid(parameter, attribute, out updatedDataType)) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidCardinality, parameter.Identifier.GetLocation(), parameterSymbol.Name)); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidCardinality, Location.None, parameter.Name)); bindingsList = null; return false; } @@ -304,9 +288,9 @@ private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax meth cardinalityValidated = true; } - string bindingName = parameter.Identifier.ValueText; + string bindingName = parameter.Name; - if (!TryCreateBindingDict(attribute, bindingName, parameter.Identifier.GetLocation(), out IDictionary? bindingDict, supportsDeferredBinding)) + if (!TryCreateBindingDict(attribute, bindingName, Location.None, out IDictionary? bindingDict, supportsDeferredBinding)) { bindingsList = null; return false; @@ -418,32 +402,35 @@ private bool DoesConverterSupportTargetType(List converterAdverti /// /// Checks for and returns any bindings found in the Return Type of the method /// - private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticModel model, bool hasHttpTrigger, bool hasOutputBinding, out IList>? bindingsList) + private bool TryGetReturnTypeBindings(IMethodSymbol method, bool hasHttpTrigger, bool hasOutputBinding, out IList>? bindingsList) { - TypeSyntax returnTypeSyntax = method.ReturnType; - ITypeSymbol? returnTypeSymbol = model.GetSymbolInfo(returnTypeSyntax).Symbol as ITypeSymbol; + ITypeSymbol? returnTypeSymbol = method.ReturnType; bindingsList = new List>(); if (returnTypeSymbol is null) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, returnTypeSyntax.GetLocation(), nameof(returnTypeSymbol))); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, Location.None, nameof(returnTypeSymbol))); bindingsList = null; return false; } if (!SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.VoidType) && - !SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.TaskType)) + !SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskType)) { // If there is a Task return type, inspect T, the inner type. - if (SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.TaskOfTType)) + if (SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskOfTType)) { - GenericNameSyntax genericSyntax = (GenericNameSyntax)returnTypeSyntax; - var innerTypeSyntax = genericSyntax.TypeArgumentList.Arguments.First(); // Generic task should only have one type argument - returnTypeSymbol = model.GetSymbolInfo(innerTypeSyntax).Symbol as ITypeSymbol; + if (returnTypeSymbol is INamedTypeSymbol namedTypeSymbol) + { + if (namedTypeSymbol.IsGenericType) + { + returnTypeSymbol = namedTypeSymbol.TypeArguments.FirstOrDefault();// Generic task should only have one type argument + } + } if (returnTypeSymbol is null) // need this check here b/c return type symbol takes on a new value from the inner argument type above { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, genericSyntax.Identifier.GetLocation(), nameof(returnTypeSymbol))); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, Location.None, nameof(returnTypeSymbol))); bindingsList = null; return false; } @@ -455,7 +442,7 @@ private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticMo } else { - if (!TryGetReturnTypePropertyBindings(returnTypeSymbol, hasHttpTrigger, hasOutputBinding, returnTypeSyntax.GetLocation(), out bindingsList)) + if (!TryGetReturnTypePropertyBindings(returnTypeSymbol, hasHttpTrigger, hasOutputBinding, out bindingsList)) { bindingsList = null; return false; @@ -466,7 +453,7 @@ private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticMo return true; } - private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool hasHttpTrigger, bool hasOutputBinding, Location returnTypeLocation, out IList>? bindingsList) + private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool hasHttpTrigger, bool hasOutputBinding, out IList>? bindingsList) { var members = returnTypeSymbol.GetMembers(); var foundHttpOutput = false; @@ -486,7 +473,7 @@ private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool { if (foundHttpOutput) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleHttpResponseTypes, returnTypeLocation, new object[] { returnTypeSymbol.Name })); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleHttpResponseTypes, Location.None, new object[] { returnTypeSymbol.Name })); bindingsList = null; return false; } @@ -505,7 +492,7 @@ private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool // validate that there's only one binding attribute per property if (foundPropertyOutputAttr) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, prop.Locations.FirstOrDefault(), new object[] { "Property", prop.Name.ToString() })); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, Location.None, new object[] { "Property", prop.Name.ToString() })); bindingsList = null; return false; } @@ -626,7 +613,15 @@ private bool TryGetAttributeProperties(AttributeData attributeData, Location? at } else { - attrProperties[namedArgument.Key] = namedArgument.Value.Value; + if (TryParseValueByType(namedArgument.Value, out object? argValue)) + { + attrProperties[namedArgument.Key] = argValue; + } + else + { + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidBindingAttributeArgument, attribLocation)); + return false; + } } } } @@ -657,14 +652,14 @@ private bool TryGetAttributeProperties(AttributeData attributeData, Location? at return true; } - private bool TryLoadConstructorArguments(AttributeData attributeData, IDictionary dict, Location? attribLocation) + private bool TryLoadConstructorArguments(AttributeData attributeData, IDictionary arguments, Location? attributeLocation) { IMethodSymbol? attribMethodSymbol = attributeData.AttributeConstructor; // Check if the attribute constructor has any parameters if (attribMethodSymbol is null) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, attribLocation, nameof(attribMethodSymbol))); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, attributeLocation, nameof(attribMethodSymbol))); return false; } @@ -677,44 +672,52 @@ private bool TryLoadConstructorArguments(AttributeData attributeData, IDictionar var arg = attributeData.ConstructorArguments[i]; - switch (arg.Kind) + if (TryParseValueByType(arg, out object? argValue)) + { + arguments[argumentName] = argValue; + } + else { - case TypedConstantKind.Error: - break; + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidBindingAttributeArgument, attributeLocation)); + return false; + } + } - case TypedConstantKind.Primitive: - dict[argumentName] = arg.Value; - break; + return true; + } - case TypedConstantKind.Enum: - var enumValue = arg.Type!.GetMembers() - .FirstOrDefault(m => m is IFieldSymbol field - && field.ConstantValue is object value - && value.Equals(arg.Value)); + private bool TryParseValueByType(TypedConstant attributeArg, out object? argValue) + { + argValue = null; - if (enumValue is null) - { - return false; - } + switch (attributeArg.Kind) + { + case TypedConstantKind.Primitive: + argValue = attributeArg.Value; + break; - // we want just the enumValue symbol's name (Admin, Anonymous, Function) - dict[argumentName] = enumValue.Name; - break; + case TypedConstantKind.Enum: + var enumValue = attributeArg.Type!.GetMembers() + .FirstOrDefault(m => m is IFieldSymbol field + && field.ConstantValue is object value + && value.Equals(attributeArg.Value)); - case TypedConstantKind.Type: - break; + if (enumValue is null) + { + return false; + } - case TypedConstantKind.Array: - var arrayValues = arg.Values.Select(a => a.Value?.ToString()).ToArray(); - dict[argumentName] = arrayValues; - break; + // we want just the enumValue symbol's name (ex: Admin, Anonymous, Function) + argValue = enumValue.Name; + break; - default: - break; - } + case TypedConstantKind.Array: + var arrayValues = attributeArg.Values.Select(a => a.Value?.ToString()).ToArray(); + argValue = arrayValues; + break; } - return true; + return argValue is not null; } /// diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs index f218d39b4..665bf2f44 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs @@ -2,8 +2,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Microsoft.Azure.Functions.Worker.Sdk.Generators @@ -35,7 +37,10 @@ public void Execute(GeneratorExecutionContext context) // attempt to parse user compilation var p = new Parser(context); - IReadOnlyList functionMetadataInfo = p.GetFunctionMetadataInfo(receiver.CandidateMethods); + var entryAssemblyFuncs = GetEntryAssemblyFunctions(receiver.CandidateMethods, context); + var dependentFuncs = GetDependentAssemblyFunctions(context); + + IReadOnlyList functionMetadataInfo = p.GetFunctionMetadataInfo(entryAssemblyFuncs.Concat(dependentFuncs).ToList()); // Proceed to generate the file if function metadata info was successfully returned if (functionMetadataInfo.Count > 0) @@ -43,7 +48,7 @@ public void Execute(GeneratorExecutionContext context) Emitter e = new(); var shouldIncludeAutoGeneratedAttributes = ShouldIncludeAutoGeneratedAttributes(context); - string result = e.Emit(functionMetadataInfo, shouldIncludeAutoGeneratedAttributes, context.CancellationToken); + string result = e.Emit(context, functionMetadataInfo, shouldIncludeAutoGeneratedAttributes); context.AddSource(Constants.FileNames.GeneratedFunctionMetadata, SourceText.From(result, Encoding.UTF8)); } @@ -57,7 +62,7 @@ public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new FunctionMethodSyntaxReceiver()); } - + private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionContext context) { if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue( @@ -68,5 +73,54 @@ private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionConte return string.Equals(value, bool.TrueString, System.StringComparison.OrdinalIgnoreCase); } + + private IEnumerable GetEntryAssemblyFunctions(List candidateMethods, GeneratorExecutionContext context) + { + foreach (MethodDeclarationSyntax method in candidateMethods) + { + var model = context.Compilation.GetSemanticModel(method.SyntaxTree); + + if (FunctionsUtil.IsValidFunctionMethod(context, context.Compilation, model, method)) + { + IMethodSymbol? methodSymbol = (IMethodSymbol)model.GetDeclaredSymbol(method)!; + yield return methodSymbol; + } + } + } + + /// + /// Collect methods with Function attributes on them from dependent/referenced assemblies. + /// + private IEnumerable GetDependentAssemblyFunctions(GeneratorExecutionContext context) + { + foreach (var assembly in context.Compilation.SourceModule.ReferencedAssemblySymbols) + { + var namespaceSymbols = assembly.GlobalNamespace.GetMembers(); + + foreach (var namespaceSymbol in namespaceSymbols) + { + var namespaceMembers = namespaceSymbol.GetMembers(); + + foreach (var m in namespaceMembers) + { + if (m is INamedTypeSymbol namedType) + { + var typeMembers = namedType.GetMembers(); + + foreach (var typeMember in typeMembers) + { + if (typeMember is IMethodSymbol method) + { + if (FunctionsUtil.IsFunctionSymbol(method, context.Compilation)) + { + yield return method; + } + } + } + } + } + } + } + } } } diff --git a/sdk/Sdk.Generators/FunctionsUtil.cs b/sdk/Sdk.Generators/FunctionsUtil.cs index d8d2ee0fc..7f5046aed 100644 --- a/sdk/Sdk.Generators/FunctionsUtil.cs +++ b/sdk/Sdk.Generators/FunctionsUtil.cs @@ -15,11 +15,9 @@ internal static class FunctionsUtil internal static bool IsValidFunctionMethod( GeneratorExecutionContext context, Compilation compilation, - SemanticModel model, - MethodDeclarationSyntax method, - out string? functionName) + SemanticModel model, + MethodDeclarationSyntax method) { - functionName = null; var methodSymbol = model.GetDeclaredSymbol(method); if (methodSymbol is null) @@ -28,12 +26,21 @@ internal static bool IsValidFunctionMethod( return false; } - foreach (var attr in methodSymbol.GetAttributes()) + if (IsFunctionSymbol(methodSymbol, compilation)) + { + return true; + } + + return false; + } + + internal static bool IsFunctionSymbol(ISymbol symbol, Compilation compilation) + { + foreach (var attr in symbol.GetAttributes()) { if (attr.AttributeClass != null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))) { - functionName = (string)attr.ConstructorArguments.First().Value!; // If this is a function attribute this won't be null return true; } } @@ -41,17 +48,49 @@ internal static bool IsValidFunctionMethod( return false; } + internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, out string? functionName) + { + functionName = null; + + var functionAttribute = symbol.GetAttributes() + .FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))); + + if (functionAttribute is not null) + { + functionName = (string)functionAttribute.ConstructorArguments.First().Value!; + return true; + } + + return false; + } + /// /// Gets the fully qualified name of the method. /// Ex: "MyNamespaceName.MyClassName.MyMethod" /// for a method called "MyMethod" inside the "MyClassName" type which is inside the "MyNamespaceName" namespace. /// - internal static string GetFullyQualifiedMethodName(MethodDeclarationSyntax method, SemanticModel semanticModel) + internal static string GetFullyQualifiedMethodName(IMethodSymbol method) { - var methodSymbol = semanticModel.GetDeclaredSymbol(method)!; - var fullyQualifiedClassName = methodSymbol.ContainingSymbol.ToDisplayString(); + var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(); + return $"{fullyQualifiedClassName}.{method.Name}"; + } + + /// + /// Gets the namespace value to be used for the auto generated types. + /// + internal static string GetNamespaceForGeneratedCode(GeneratorExecutionContext context) + { + // If csproj has the msbuild property specified, use it's value. + if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(Constants.BuildProperties.GeneratedCodeNamespace, out var namespaceValue) + && !string.IsNullOrWhiteSpace(namespaceValue)) + { + return namespaceValue; + } + + // Get the "RootNamespace" msbuild property value.(This gets populated in Microsoft.NET.Sdk.props and can be overridden by user in their function app) + context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(Constants.BuildProperties.MSBuildRootNamespace, out var rootNamespaceValue); - return $"{fullyQualifiedClassName}.{method.Identifier.ValueText}"; + return rootNamespaceValue!; } } } diff --git a/sdk/Sdk.Generators/Sdk.Generators.csproj b/sdk/Sdk.Generators/Sdk.Generators.csproj index eaa84c73b..fef28b16e 100644 --- a/sdk/Sdk.Generators/Sdk.Generators.csproj +++ b/sdk/Sdk.Generators/Sdk.Generators.csproj @@ -10,6 +10,7 @@ false true 1 + 2 true @@ -35,4 +36,4 @@ - \ No newline at end of file + diff --git a/sdk/Sdk/Constants.cs b/sdk/Sdk/Constants.cs index 227f3b14f..c0731ef64 100644 --- a/sdk/Sdk/Constants.cs +++ b/sdk/Sdk/Constants.cs @@ -43,10 +43,7 @@ internal static class Constants // NetFramework internal const string NetCoreApp31 = "netcoreapp3.1"; internal const string Net60 = "net6.0"; - internal const string Net70 = "net7.0"; - internal const string Net50 = "net5.0"; internal const string NetCoreApp = ".NETCoreApp"; - internal const string NetCoreVersion7 = "v7.0"; internal const string NetCoreVersion6 = "v6.0"; internal const string NetCoreVersion31 = "v3.1"; internal const string AzureFunctionsVersion3 = "v3"; diff --git a/sdk/Sdk/ExtensionsCsprojGenerator.cs b/sdk/Sdk/ExtensionsCsprojGenerator.cs index c54553c9c..c11097cb2 100644 --- a/sdk/Sdk/ExtensionsCsprojGenerator.cs +++ b/sdk/Sdk/ExtensionsCsprojGenerator.cs @@ -68,11 +68,6 @@ internal string GetCsProjContent() { targetFramework = Constants.NetCoreApp31; } - - if (_targetFrameworkVersion.Equals(Constants.NetCoreVersion7, StringComparison.OrdinalIgnoreCase)) - { - targetFramework = Constants.Net70; - } } string netSdkVersion = _azureFunctionsVersion.StartsWith(Constants.AzureFunctionsVersion3, StringComparison.OrdinalIgnoreCase) ? "3.1.2" : "4.2.0"; diff --git a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.props b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.props index bac53404a..d24ff04fb 100644 --- a/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.props +++ b/sdk/Sdk/Targets/Microsoft.Azure.Functions.Worker.Sdk.props @@ -25,6 +25,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and +