Skip to content

Commit 5387249

Browse files
committed
Adding binding feature. Fixing issue with value binding scope
1 parent 7efc1ee commit 5387249

14 files changed

+267
-50
lines changed

src/DotNetWorker/Configuration/HostBuilderExtensions.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ public static IHostBuilder ConfigureFunctionsWorker(this IHostBuilder builder, A
4545

4646
public static IFunctionsWorkerApplicationBuilder UseFunctionExecutionMiddleware(this IFunctionsWorkerApplicationBuilder builder)
4747
{
48-
// We want to keep this internal for now.
49-
builder.UseConverterMiddleware();
50-
5148
builder.UseOutputBindingsMiddleware();
5249

5350
builder.Services.AddSingleton<FunctionExecutionMiddleware>();
@@ -65,23 +62,6 @@ public static IFunctionsWorkerApplicationBuilder UseFunctionExecutionMiddleware(
6562
return builder;
6663
}
6764

68-
internal static IFunctionsWorkerApplicationBuilder UseConverterMiddleware(this IFunctionsWorkerApplicationBuilder builder)
69-
{
70-
builder.Services.AddSingleton<ConverterMiddleware>();
71-
72-
builder.Use(next =>
73-
{
74-
return context =>
75-
{
76-
var middleware = context.InstanceServices.GetRequiredService<ConverterMiddleware>();
77-
78-
return middleware.Invoke(context, next);
79-
};
80-
});
81-
82-
return builder;
83-
}
84-
8565
internal static IFunctionsWorkerApplicationBuilder UseOutputBindingsMiddleware(this IFunctionsWorkerApplicationBuilder builder)
8666
{
8767
builder.Services.AddSingleton<OutputBindingsMiddleware>();

src/DotNetWorker/Configuration/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Grpc.Net.Client;
88
using Microsoft.Azure.Functions.Worker;
99
using Microsoft.Azure.Functions.Worker.Configuration;
10+
using Microsoft.Azure.Functions.Worker.Context.Features;
1011
using Microsoft.Azure.Functions.Worker.Converters;
1112
using Microsoft.Azure.Functions.Worker.Diagnostics;
1213
using Microsoft.Azure.Functions.Worker.Invocation;
@@ -60,6 +61,7 @@ public static IFunctionsWorkerApplicationBuilder AddFunctionsWorker(this IServic
6061

6162
// Invocation Features
6263
services.TryAddSingleton<IInvocationFeaturesFactory, DefaultInvocationFeaturesFactory>();
64+
services.AddSingleton<IInvocationFeatureProvider, DefaultBindingFeatureProvider>();
6365

6466
// Function Definition
6567
services.AddSingleton<IFunctionDefinitionFactory, DefaultFunctionDefinitionFactory>();
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+
using System.Collections.Generic;
6+
using Microsoft.Azure.Functions.Worker.Converters;
7+
8+
namespace Microsoft.Azure.Functions.Worker.Context.Features
9+
{
10+
internal class DefaultBindingFeatureProvider : IInvocationFeatureProvider
11+
{
12+
private readonly static Type _featureType = typeof(IModelBindingFeature);
13+
private readonly IEnumerable<IConverter> _converters;
14+
15+
public DefaultBindingFeatureProvider(IEnumerable<IConverter> converters)
16+
{
17+
_converters = converters ?? throw new ArgumentNullException(nameof(converters));
18+
}
19+
20+
public bool TryCreate(Type type, out object? feature)
21+
{
22+
feature = type == _featureType
23+
? new DefaultModelBindingFeature(_converters)
24+
: null;
25+
26+
return feature is not null;
27+
}
28+
}
29+
}

src/DotNetWorker/Converters/ConverterMiddleware.cs renamed to src/DotNetWorker/Context/Features/DefaultModelBindingFeature.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,51 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Threading.Tasks;
7-
using Microsoft.Azure.Functions.Worker.Pipeline;
6+
using System.Linq;
7+
using Microsoft.Azure.Functions.Worker.Converters;
8+
using Microsoft.Azure.Functions.Worker.Definition;
89

9-
namespace Microsoft.Azure.Functions.Worker.Converters
10+
namespace Microsoft.Azure.Functions.Worker.Context.Features
1011
{
11-
internal sealed class ConverterMiddleware
12+
internal class DefaultModelBindingFeature : IModelBindingFeature
1213
{
1314
private readonly IEnumerable<IConverter> _converters;
15+
private bool _inputBound;
16+
private object?[]? _parameterValues;
1417

15-
public ConverterMiddleware(IEnumerable<IConverter> converters)
18+
public DefaultModelBindingFeature(IEnumerable<IConverter> converters)
1619
{
1720
_converters = converters ?? throw new ArgumentNullException(nameof(converters));
1821
}
1922

20-
public Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
23+
public object?[]? InputArguments => _parameterValues;
24+
25+
public object?[] BindFunctionInput(FunctionContext context)
2126
{
22-
// TODO: The value needs to be moved to the context
23-
// parameters values should be properly associated with the context
24-
// and support disposal.
25-
foreach (var param in context.FunctionDefinition.Parameters)
27+
if (_inputBound)
2628
{
29+
throw new InvalidOperationException("Duplicate binding call detected. " +
30+
$"Input parameters can only be bound to arguments once. Use the {nameof(InputArguments)} property to inspect values.");
31+
}
32+
33+
_parameterValues = new object?[context.FunctionDefinition.Parameters.Length];
34+
_inputBound = true;
35+
36+
for (int i = 0; i < _parameterValues.Length; i++)
37+
{
38+
FunctionParameter param = context.FunctionDefinition.Parameters[i];
39+
2740
object? source = context.Invocation.ValueProvider.GetValue(param.Name, context);
2841
var converterContext = new DefaultConverterContext(param, source, context);
42+
2943
if (TryConvert(converterContext, out object? target))
3044
{
31-
param.Value = target;
45+
_parameterValues[i] = target;
3246
continue;
3347
}
3448
}
3549

36-
return next(context);
50+
return _parameterValues;
3751
}
3852

3953
internal bool TryConvert(ConverterContext context, out object? target)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace Microsoft.Azure.Functions.Worker.Context.Features
10+
{
11+
internal interface IModelBindingFeature
12+
{
13+
object?[]? InputArguments { get; }
14+
15+
object?[] BindFunctionInput(FunctionContext context);
16+
}
17+
}

src/DotNetWorker/Definition/FunctionParameter.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,53 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Collections.ObjectModel;
59

610
namespace Microsoft.Azure.Functions.Worker.Definition
711
{
12+
/// <summary>
13+
/// Represents a parameter defined by the target function.
14+
/// </summary>
815
public class FunctionParameter
916
{
17+
/// <summary>
18+
/// Creates an instance of the <see cref="FunctionParameter"/> class.
19+
/// </summary>
20+
/// <param name="name">The parameter name.</param>
21+
/// <param name="type">The <see cref="System.Type"/> of the parameter.</param>
1022
public FunctionParameter(string name, Type type)
23+
:this(name, type, ImmutableDictionary<string, object>.Empty)
1124
{
12-
Name = name;
13-
Type = type;
1425
}
1526

27+
/// <summary>
28+
/// Creates an instance of the <see cref="FunctionParameter"/> class.
29+
/// </summary>
30+
/// <param name="name">The parameter name.</param>
31+
/// <param name="type">The <see cref="System.Type"/> of the parameter.</param>
32+
public FunctionParameter(string name, Type type, IReadOnlyDictionary<string, object> properties)
33+
{
34+
Name = name ?? throw new ArgumentNullException(nameof(name));
35+
Type = type ?? throw new ArgumentNullException(nameof(type));
36+
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
37+
}
38+
39+
/// <summary>
40+
/// Gets the parameter name.
41+
/// </summary>
1642
public string Name { get; }
1743

44+
/// <summary>
45+
/// Gets the parameter <see cref="System.Type"/>.
46+
/// </summary>
1847
public Type Type { get; }
1948

20-
// TODO: Pop out to Context (or Invocation)
21-
public object? Value { get; set; }
49+
/// <summary>
50+
/// A dictionary holding properties of this parameter.
51+
/// </summary>
52+
public IReadOnlyDictionary<string, object> Properties { get; }
2253
}
2354
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
using Microsoft.Azure.Functions.Worker.Context.Features;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.Azure.Functions.Worker.Invocation
9+
{
10+
internal partial class DefaultFunctionExecutor
11+
{
12+
private static class Log
13+
{
14+
private static readonly Action<ILogger, string, string, Exception?> _functionBindingFeatureUnavailable =
15+
WorkerMessage.Define<string, string>(LogLevel.Warning, new EventId(2, nameof(FunctionBindingFeatureUnavailable)),
16+
"The feature " + nameof(IModelBindingFeature) + " was not available for invocation '{invocationId}' of function '{functionName}'. Unable to process input bindings.");
17+
18+
public static void FunctionBindingFeatureUnavailable(ILogger<DefaultFunctionExecutor> logger, FunctionContext context)
19+
{
20+
_functionBindingFeatureUnavailable(logger, context.Invocation.InvocationId, context.Invocation.FunctionId, null);
21+
}
22+
}
23+
}
24+
}

src/DotNetWorker/Invocation/DefaultFunctionExecutor.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,39 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Linq;
65
using System.Threading.Tasks;
6+
using Microsoft.Azure.Functions.Worker.Context.Features;
7+
using Microsoft.Extensions.Logging;
78

89
namespace Microsoft.Azure.Functions.Worker.Invocation
910
{
10-
internal class DefaultFunctionExecutor : IFunctionExecutor
11+
internal partial class DefaultFunctionExecutor : IFunctionExecutor
1112
{
13+
private readonly ILogger<DefaultFunctionExecutor> _logger;
14+
15+
public DefaultFunctionExecutor(ILogger<DefaultFunctionExecutor> logger)
16+
{
17+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
18+
}
19+
1220
public async Task ExecuteAsync(FunctionContext context)
1321
{
1422
var invoker = context.FunctionDefinition.Invoker;
1523
object? instance = invoker.CreateInstance(context.InstanceServices);
16-
object? result = await invoker.InvokeAsync(instance, context.FunctionDefinition.Parameters.Select(p => p.Value).ToArray());
24+
var bindingFeature = context.Features.Get<IModelBindingFeature>();
25+
26+
object?[] inputArguments;
27+
if (bindingFeature is null)
28+
{
29+
Log.FunctionBindingFeatureUnavailable(_logger, context);
30+
inputArguments = new object?[context.FunctionDefinition.Parameters.Length];
31+
}
32+
else
33+
{
34+
inputArguments = bindingFeature.BindFunctionInput(context);
35+
}
36+
37+
object? result = await invoker.InvokeAsync(instance, inputArguments);
1738

1839
context.InvocationResult = result;
1940
}

test/DotNetWorkerTests/Converters/ConverterMiddlewareTests.cs renamed to test/DotNetWorkerTests/Converters/DefaultBinderTests.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
using System.Collections.Generic;
55
using System.Text.Json;
66
using Azure.Core.Serialization;
7+
using Microsoft.Azure.Functions.Worker.Context.Features;
78
using Microsoft.Azure.Functions.Worker.Converters;
89
using Xunit;
910

1011
namespace Microsoft.Azure.Functions.Worker.Tests.Converters
1112
{
12-
public class ConverterMiddlewareTests
13+
public class DefaultBinderTests
1314
{
14-
private ConverterMiddleware _paramConverterManager;
15+
private DefaultModelBindingFeature _paramConverterManager;
1516

16-
public ConverterMiddlewareTests()
17+
public DefaultBinderTests()
1718
{
1819
// Test overriding serialization settings.
1920
var serializer = new JsonObjectSerializer(new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
20-
_paramConverterManager = TestUtility.GetDefaultConverterMiddleware(o => o.Serializer = serializer);
21+
_paramConverterManager = TestUtility.GetDefaultBindingFeature(o => o.Serializer = serializer);
2122
}
2223

2324
[Fact]

0 commit comments

Comments
 (0)