Skip to content

Commit 14cc888

Browse files
authored
Adding support for overriding the binding property name in function metadata (#1086)
* Add support for overriding the property name used when building binding entry for function metatdata generation. * - Bit of cleanup - Version bump for Storage.Blobs extension * Comments cleanup * Update version of Worker.Extensions.Storage package. * Clean up based on PR feedback * Addressing some of PR feedback * SDK package bump to preview2 * Rebased on main. Fixed release notes in sdk and blob extension
1 parent 07068cb commit 14cc888

File tree

11 files changed

+126
-10
lines changed

11 files changed

+126
-10
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Azure.Functions.Worker.Extensions.Abstractions
7+
{
8+
/// <summary>
9+
/// Specifies the binding property name that is used when generating function metadata.
10+
/// </summary>
11+
[AttributeUsage(AttributeTargets.Property)]
12+
public sealed class BindingPropertyNameAttribute : Attribute
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of <see cref="BindingPropertyNameAttribute"/> with the specified property name.
16+
/// </summary>
17+
/// <param name="bindingPropertyName">The name of the property to be used when generating function metadata.</param>
18+
/// <exception cref="ArgumentNullException">Throws when bindingPropertyName is null.</exception>
19+
public BindingPropertyNameAttribute(string bindingPropertyName)
20+
{
21+
BindingPropertyName = bindingPropertyName ?? throw new ArgumentNullException(nameof(bindingPropertyName));
22+
}
23+
24+
/// <summary>
25+
/// Gets the binding property name to be used for metadata generation.
26+
/// </summary>
27+
public string BindingPropertyName { get; }
28+
}
29+
}

extensions/Worker.Extensions.Storage.Blobs/release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
<!-- Please add your release notes in the following format:
33
- My change description (#PR/#issue)
44
-->
5+
- Overriding binding property name for "BlobPath" property of BlobTrigger (#1086)

extensions/Worker.Extensions.Storage.Blobs/src/BlobTriggerAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4-
using System;
54
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
65

76
namespace Microsoft.Azure.Functions.Worker
@@ -28,6 +27,7 @@ public BlobTriggerAttribute(string blobPath)
2827
/// The blob portion of the path can contain tokens in curly braces to indicate a pattern to match. The matched
2928
/// name can be used in other binding attributes to define the output name of a Job function.
3029
/// </remarks>
30+
[BindingPropertyName("path")]
3131
public string BlobPath
3232
{
3333
get { return _blobPath; }

extensions/Worker.Extensions.Storage.Blobs/src/Worker.Extensions.Storage.Blobs.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Description>Azure Blob Storage extensions for .NET isolated functions</Description>
77

88
<!--Version information-->
9-
<VersionPrefix>5.0.0</VersionPrefix>
9+
<VersionPrefix>5.0.1</VersionPrefix>
1010

1111
<!--Temporarily opting out of documentation. Pending documentation-->
1212
<GenerateDocumentationFile>false</GenerateDocumentationFile>

extensions/Worker.Extensions.Storage/src/Worker.Extensions.Storage.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Description>Azure Storage extensions for .NET isolated functions</Description>
77

88
<!--Version information-->
9-
<VersionPrefix>5.0.0</VersionPrefix>
9+
<VersionPrefix>5.0.1</VersionPrefix>
1010

1111
<!--Temporarily opting out of documentation. Pending documentation-->
1212
<GenerateDocumentationFile>false</GenerateDocumentationFile>

sdk/Sdk/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ internal static class Constants
1414
internal const string DefaultValueAttributeType = "Microsoft.Azure.Functions.Worker.Extensions.Abstractions.DefaultValueAttribute";
1515
internal const string FixedDelayRetryAttributeType = "Microsoft.Azure.Functions.Worker.FixedDelayRetryAttribute";
1616
internal const string ExponentialBackoffRetryAttributeType = "Microsoft.Azure.Functions.Worker.ExponentialBackoffRetryAttribute";
17+
internal const string BindingPropertyNameAttributeType = "Microsoft.Azure.Functions.Worker.Extensions.Abstractions.BindingPropertyNameAttribute";
1718

1819
// System types
1920
internal const string IEnumerableType = "System.Collections.IEnumerable";

sdk/Sdk/FunctionMetadataGenerator.cs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public IEnumerable<SdkFunctionMetadata> GenerateFunctionMetadata(string assembly
5454

5555
var functions = new List<SdkFunctionMetadata>();
5656

57-
_logger.LogMessage($"Found { targetAssemblies.Count} assemblies to evaluate in '{sourcePath}':");
57+
_logger.LogMessage($"Found {targetAssemblies.Count} assemblies to evaluate in '{sourcePath}':");
5858

5959
foreach (var path in targetAssemblies)
6060
{
@@ -202,7 +202,7 @@ private bool TryCreateFunctionMetadata(MethodDefinition method, out SdkFunctionM
202202
// if a retry attribute is defined, add it to the function.
203203
if (retryOptions != null)
204204
{
205-
function.Retry = retryOptions;
205+
function.Retry = retryOptions;
206206
}
207207

208208
return true;
@@ -425,6 +425,54 @@ private static void AddBindingMetadata(IList<ExpandoObject> bindingMetadata, Cus
425425
bindingMetadata.Add(binding);
426426
}
427427

428+
/// <summary>
429+
/// Creates a map of property names and its binding property names, if specified from the attribute passed in.
430+
/// </summary>
431+
/// <remarks>
432+
/// Example output generated from BlobTriggerAttribute.
433+
/// {
434+
/// "BlobPath" : "path"
435+
/// }
436+
///
437+
/// In BlobTriggerAttribute type, the "BlobPath" property is decorated with "MetadataBindingPropertyName" attribute
438+
/// where "path" is provided as the value to be used when generating metadata binding data, as shown below.
439+
///
440+
/// [MetadataBindingPropertyName("path")]
441+
/// public string BlobPath { get; set; }
442+
///
443+
/// </remarks>
444+
/// <param name="attribute">The CustomAttribute instance to use for building the map. This will be a binding attribute
445+
/// used to decorate the function parameter. Ex: BlobTrigger</param>
446+
/// <returns>A dictionary containing the mapping.</returns>
447+
private static IDictionary<string, string> GetPropertyNameAliasMapping(CustomAttribute attribute)
448+
{
449+
var bindingNameAliasMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
450+
TypeDefinition typeDefinition = attribute.AttributeType.Resolve();
451+
452+
foreach (var prop in typeDefinition.Properties)
453+
{
454+
var bindingPropAttr = prop.CustomAttributes.FirstOrDefault(a =>
455+
a.AttributeType.FullName.Equals(Constants.BindingPropertyNameAttributeType));
456+
457+
// The "MetadataBindingPropertyName" attribute has only one constructor which takes the name to be used.
458+
var firstArgument = bindingPropAttr?.ConstructorArguments.Single();
459+
if (firstArgument?.Value is null)
460+
{
461+
continue;
462+
}
463+
464+
if (firstArgument.Value.Value is not string bindingNameArgumentValue)
465+
{
466+
throw new ArgumentException(
467+
$"The type {Constants.BindingPropertyNameAttributeType} is expected to have a single constructor with a parameter of type {nameof(String)}");
468+
}
469+
470+
bindingNameAliasMap[prop.Name] = bindingNameArgumentValue;
471+
}
472+
473+
return bindingNameAliasMap;
474+
}
475+
428476
private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute attribute, string bindingType, TypeReference parameterType, string? parameterName)
429477
{
430478
ExpandoObject binding = new ExpandoObject();
@@ -450,9 +498,18 @@ private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute a
450498
bindingDict["DataType"] = "Binary";
451499
}
452500

501+
var bindingNameAliasDict = GetPropertyNameAliasMapping(attribute);
502+
453503
foreach (var property in attribute.GetAllDefinedProperties())
454504
{
455-
bindingDict.Add(property.Key, property.Value);
505+
var propName = property.Key;
506+
507+
508+
var propertyName = bindingNameAliasDict.TryGetValue(property.Key, out var overriddenPropertyName)
509+
? overriddenPropertyName
510+
: property.Key;
511+
512+
bindingDict.Add(propertyName, property.Value);
456513
}
457514

458515
// Determine if we should set the "Cardinality" property based on
@@ -493,7 +550,7 @@ private static ExpandoObject BuildBindingMetadataFromAttribute(CustomAttribute a
493550
else
494551
{
495552
throw new FunctionsMetadataGenerationException("Function is configured to process events in batches but parameter type is not iterable. " +
496-
$"Change parameter named '{ parameterName }' to be an IEnumerable type or set 'IsBatched' to false on your '{attribute.AttributeType.Name.Replace("Attribute", "")}' attribute.");
553+
$"Change parameter named '{parameterName}' to be an IEnumerable type or set 'IsBatched' to false on your '{attribute.AttributeType.Name.Replace("Attribute", "")}' attribute.");
497554
}
498555
}
499556
// Batching set to false

sdk/Sdk/Sdk.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<MinorProductVersion>8</MinorProductVersion>
5-
<VersionSuffix>-preview1</VersionSuffix>
5+
<VersionSuffix>-preview2</VersionSuffix>
66
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks>
77
<PackageId>Microsoft.Azure.Functions.Worker.Sdk</PackageId>
88
<Description>This package provides development time support for the Azure Functions .NET Worker.</Description>

sdk/release_notes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
<!-- Please add your release notes in the following format:
33
- My change description (#PR/#issue)
44
-->
5-
- Improvements to WebJobsAttributesNotSupported analyzer. (#1092)
5+
- Improvements to WebJobsAttributesNotSupported analyzer. (#1092)
6+
- Adding support for overriding the binding property name in function metadata (#1086)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
5+
using Xunit;
6+
7+
namespace Microsoft.Azure.Functions.Worker.Tests
8+
{
9+
public class BindingNameAttributeTest
10+
{
11+
[Fact]
12+
public void Ensure_BindingPropertyNameAttribute_Has_Single_Constructor()
13+
{
14+
// This test is to ensure that the BindingPropertyNameAttribute constructors are not modified
15+
// as our metadata generation code makes the assumption that this type has only one
16+
// constructor which accepts the binding property name which is of string type.
17+
18+
var allConstructors = typeof(BindingPropertyNameAttribute).GetConstructors();
19+
20+
Assert.Single(allConstructors);
21+
22+
var constructorParameters = allConstructors[0].GetParameters();
23+
Assert.Single(constructorParameters);
24+
Assert.Equal(typeof(string), constructorParameters[0].ParameterType);
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)