Skip to content

Commit

Permalink
Implement bypass deferred binding (#1462)
Browse files Browse the repository at this point in the history
Implement bypass deferred binding
  • Loading branch information
surgupta-msft committed May 12, 2023
1 parent cb93c71 commit ad3a33c
Show file tree
Hide file tree
Showing 32 changed files with 1,757 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
namespace Microsoft.Azure.Functions.Worker.Extensions.Abstractions
{
/// <summary>
/// Specifies if a binding attribute supports deferred binding when generating function metadata.
/// This is to be used on input, output or trigger attributes that support deferred binding.
/// Specifies if a converter supports deferred binding when generating function metadata.
/// This is to be used on converters that support deferred binding.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SupportsDeferredBindingAttribute : Attribute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
using Microsoft.Azure.Functions.Worker.Extensions.CosmosDB;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;

namespace Microsoft.Azure.Functions.Worker
{
/// <summary>
/// Converter to bind Cosmos DB type parameters.
/// </summary>
[SupportsDeferredBinding]
internal class CosmosDBConverter : IInputConverter
{
private readonly IOptionsSnapshot<CosmosDBBindingOptions> _cosmosOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;

namespace Microsoft.Azure.Functions.Worker
{
[SupportsDeferredBinding]
[AllowConverterFallback(false)]
[InputConverter(typeof(CosmosDBConverter))]
public sealed class CosmosDBInputAttribute : InputBindingAttribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
Expand All @@ -24,11 +24,6 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui
applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory
applicationBuilder.Services.AddOptions<CosmosDBBindingOptions>();
applicationBuilder.Services.AddSingleton<IConfigureOptions<CosmosDBBindingOptions>, CosmosDBBindingOptionsSetup>();

applicationBuilder.Services.Configure<WorkerOptions>((workerOption) =>
{
workerOption.InputConverters.RegisterAt<CosmosDBConverter>(0);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;

namespace Microsoft.Azure.Functions.Worker
{
[SupportsDeferredBinding]
[AllowConverterFallback(false)]
[InputConverter(typeof(BlobStorageConverter))]
public sealed class BlobInputAttribute : InputBindingAttribute
{
private readonly string _blobPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
using Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;

namespace Microsoft.Azure.Functions.Worker
{
/// <summary>
/// Converter to bind Blob Storage type parameters.
/// </summary>
[SupportsDeferredBinding]
internal class BlobStorageConverter : IInputConverter
{
private readonly IOptions<WorkerOptions> _workerOptions;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;

namespace Microsoft.Azure.Functions.Worker
{
[SupportsDeferredBinding]
[AllowConverterFallback(true)]
[InputConverter(typeof(BlobStorageConverter))]
public sealed class BlobTriggerAttribute : TriggerBindingAttribute
{
private readonly string _blobPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ public override void Configure(IFunctionsWorkerApplicationBuilder applicationBui
applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory
applicationBuilder.Services.AddOptions<BlobStorageBindingOptions>();
applicationBuilder.Services.AddSingleton<IConfigureOptions<BlobStorageBindingOptions>, BlobStorageBindingOptionsSetup>();

applicationBuilder.Services.Configure<WorkerOptions>((workerOption) =>
{
workerOption.InputConverters.RegisterAt<BlobStorageConverter>(0);
});
}
}
}
2 changes: 2 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
<!-- Please add your release notes in the following format:
- My change description (#PR/#issue)
-->

- Implementation for bypass deferred binding (#1462/#1495)
6 changes: 6 additions & 0 deletions samples/WorkerBindingSamples/NuGet.Config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
55 changes: 28 additions & 27 deletions samples/WorkerBindingSamples/WorkerBindingSamples.csproj
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.12.1-preview1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.9.0-preview1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.1.0-preview1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\extensions\Worker.Extensions.CosmosDB\src\Worker.Extensions.CosmosDB.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.9.0-preview1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.Storage\src\Worker.Extensions.Storage.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker.ApplicationInsights\DotNetWorker.ApplicationInsights.csproj" />
<ProjectReference Include="..\..\src\DotNetWorker\DotNetWorker.csproj" />
<ProjectReference Include="..\..\extensions\Worker.Extensions.CosmosDB\src\Worker.Extensions.CosmosDB.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions sdk/Sdk/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal static class Constants
internal const string ExponentialBackoffRetryAttributeType = "Microsoft.Azure.Functions.Worker.ExponentialBackoffRetryAttribute";
internal const string BindingPropertyNameAttributeType = "Microsoft.Azure.Functions.Worker.Extensions.Abstractions.BindingPropertyNameAttribute";
internal const string SupportsDeferredBindingAttributeType = "Microsoft.Azure.Functions.Worker.Extensions.Abstractions.SupportsDeferredBindingAttribute";
internal const string InputConverterAttributeType = "Microsoft.Azure.Functions.Worker.Converters.InputConverterAttribute";
internal const string SupportedConverterTypeAttributeType = "Microsoft.Azure.Functions.Worker.Converters.SupportedConverterTypeAttribute";

// System types
internal const string IEnumerableType = "System.Collections.IEnumerable";
Expand Down
77 changes: 73 additions & 4 deletions sdk/Sdk/FunctionMetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Collections.Generic;

namespace Microsoft.Azure.Functions.Worker.Sdk
{
Expand Down Expand Up @@ -487,7 +488,7 @@ private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute a

// For extensions that support deferred binding, set the supportsDeferredBinding property so parameters are bound by the worker
// Only use deferred binding for input and trigger bindings, output is out not currently supported
if (SupportsDeferredBinding(attribute) && direction != Constants.OutputBindingDirection)
if (SupportsDeferredBinding(attribute, parameterType) && direction != Constants.OutputBindingDirection)
{
bindingProperties.Add(Constants.SupportsDeferredBindingProperty, Boolean.TrueString);
}
Expand Down Expand Up @@ -795,7 +796,8 @@ private static string GetBindingDirection(CustomAttribute attribute)
return Constants.InputBindingDirection;
}

private static bool SupportsDeferredBinding(CustomAttribute attribute)
// Input and Trigger Binding Attribute will be checked for supporting deferred binding
private static bool SupportsDeferredBinding(CustomAttribute attribute, TypeReference bindingType)
{
var typeDefinition = attribute?.AttributeType?.Resolve();

Expand All @@ -804,8 +806,75 @@ private static bool SupportsDeferredBinding(CustomAttribute attribute)
return false;
}

return typeDefinition.CustomAttributes
.Any(a => string.Equals(a.AttributeType.FullName, Constants.SupportsDeferredBindingAttributeType, StringComparison.Ordinal));
// checking attributes advertised by the binding attribute
foreach (CustomAttribute bindingAttribute in typeDefinition.CustomAttributes)
{
if (string.Equals(bindingAttribute.AttributeType.FullName, Constants.InputConverterAttributeType, StringComparison.Ordinal))
{
// InputConverterAttribute will have supported converter type
foreach (var customAttribute in bindingAttribute.ConstructorArguments)
{
if (DoesConverterSupportDeferredBinding(customAttribute, bindingType))
{
return true;
}
}
}
}

return false;
}

private static bool DoesConverterSupportDeferredBinding(CustomAttributeArgument customAttribute, TypeReference bindingType)
{
var typeReferenceValue = customAttribute.Value as TypeReference;
var typeReferenceCustomAttributes = typeReferenceValue?.Resolve().CustomAttributes;

// Attributes advertised by converter
if (typeReferenceCustomAttributes is not null)
{
bool converterAdvertisesDeferredBindingSupport = typeReferenceCustomAttributes.Any(a => string.Equals(a.AttributeType.FullName, Constants.SupportsDeferredBindingAttributeType, StringComparison.Ordinal));

if (converterAdvertisesDeferredBindingSupport)
{
bool converterAdvertisesTypes = typeReferenceCustomAttributes.Any(a => string.Equals(a.AttributeType.FullName, Constants.SupportedConverterTypeAttributeType, StringComparison.Ordinal));

if (!converterAdvertisesTypes)
{
// If a converter advertises deferred binding but does not explictly advertise any types then DeferredBinding will be supported for all the types
return true;
}

return DoesConverterSupportTargetType(typeReferenceCustomAttributes, bindingType);
}
}

return false;
}

private static bool DoesConverterSupportTargetType(Collection<CustomAttribute> customAttributes, TypeReference bindingType)
{
// Parse attributes advertised by converter
foreach (CustomAttribute attribute in customAttributes)
{
if (string.Equals(attribute.AttributeType.FullName, Constants.SupportedConverterTypeAttributeType, StringComparison.Ordinal))
{
foreach (CustomAttributeArgument element in attribute.ConstructorArguments)
{
if (string.Equals(element.Type.FullName, typeof(Type).FullName, StringComparison.Ordinal))
{
var supportedType = element.Value as TypeReference;

if (supportedType is not null && string.Equals(supportedType.FullName, bindingType.FullName, StringComparison.Ordinal))
{
return true;
}
}
}
}
}

return false;
}

private static bool IsOutputBindingType(CustomAttribute attribute)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Converters;
Expand Down Expand Up @@ -69,18 +70,29 @@ public async ValueTask<FunctionInputBindingResult> BindFunctionInputAsync(Functi
}
else
{
IReadOnlyDictionary<string, object> properties = ImmutableDictionary<string, object>.Empty;
var properties = new Dictionary<string, object>();

// Pass info about specific input converter type defined for this parameter, if present.
if (param.Properties.TryGetValue(PropertyBagKeys.ConverterType, out var converterTypeAssemblyFullName))
{
properties = new Dictionary<string, object>()
properties.Add(PropertyBagKeys.ConverterType, converterTypeAssemblyFullName);
}

// Pass info about the flag to allow fallback to default converters defined for this parameter, if present.
if (param.Properties.TryGetValue(PropertyBagKeys.AllowConverterFallback, out var flag))
{
properties.Add(PropertyBagKeys.AllowConverterFallback, flag);
}

// Pass info about input converter types defined for this parameter, if present.
if (param.Properties.TryGetValue(PropertyBagKeys.BindingAttributeSupportedConverters, out var converters))
{
{ PropertyBagKeys.ConverterType, converterTypeAssemblyFullName }
};
properties.Add(PropertyBagKeys.BindingAttributeSupportedConverters, converters);
}

var converterContext = _converterContextFactory.Create(param.Type, source, context, properties);
var converterContext = _converterContextFactory.Create(param.Type, source, context, properties.Count() != 0
? properties.ToImmutableDictionary()
: ImmutableDictionary<string, object>.Empty);

bindingResult = await inputConversionFeature.ConvertAsync(converterContext);
inputBindingCache[cacheKey] = bindingResult;
Expand Down

0 comments on commit ad3a33c

Please sign in to comment.