src/Microsoft.Azure.WebJobs.Host/Triggers/TriggeredFunctionBinding.cs (106 lines of code) (raw):
// 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 System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Protocols;
namespace Microsoft.Azure.WebJobs.Host.Triggers
{
internal class TriggeredFunctionBinding<TTriggerValue> : ITriggeredFunctionBinding<TTriggerValue>
{
private readonly FunctionDescriptor _descriptor;
private readonly string _triggerParameterName;
private readonly ITriggerBinding _triggerBinding;
private readonly IReadOnlyDictionary<string, IBinding> _nonTriggerBindings;
private readonly SingletonManager _singletonManager;
public TriggeredFunctionBinding(FunctionDescriptor descriptor, string triggerParameterName, ITriggerBinding triggerBinding,
IReadOnlyDictionary<string, IBinding> nonTriggerBindings, SingletonManager singletonManager)
{
_descriptor = descriptor;
_triggerParameterName = triggerParameterName;
_triggerBinding = triggerBinding;
_nonTriggerBindings = nonTriggerBindings;
_singletonManager = singletonManager;
}
public async Task<IReadOnlyDictionary<string, IValueProvider>> BindAsync(ValueBindingContext context, TTriggerValue value)
{
return await BindCoreAsync(context, value, null);
}
public async Task<IReadOnlyDictionary<string, IValueProvider>> BindAsync(ValueBindingContext context,
IDictionary<string, object> parameters)
{
if (parameters == null || !parameters.TryGetValue(_triggerParameterName, out object value))
{
throw new InvalidOperationException($"Missing value for trigger parameter '{_triggerParameterName}'.");
}
return await BindCoreAsync(context, value, parameters);
}
private async Task<IReadOnlyDictionary<string, IValueProvider>> BindCoreAsync(ValueBindingContext context, object value, IDictionary<string, object> parameters)
{
// Account for below: 1 + _nonTriggerBindings + maybe 1 more, to avoid a resize in this hot path
Dictionary<string, IValueProvider> valueProviders = new Dictionary<string, IValueProvider>(1 + _nonTriggerBindings.Count + 1);
IValueProvider triggerProvider;
IReadOnlyDictionary<string, object> bindingData;
IValueBinder triggerReturnValueProvider= null;
try
{
ITriggerData triggerData = await _triggerBinding.BindAsync(value, context);
triggerProvider = triggerData.ValueProvider;
bindingData = triggerData.BindingData;
triggerReturnValueProvider = (triggerData as TriggerData)?.ReturnValueProvider;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception exception)
{
triggerProvider = new BindingExceptionValueProvider(_triggerParameterName, exception);
bindingData = null;
}
valueProviders.Add(_triggerParameterName, triggerProvider);
// Bind Singleton if specified
SingletonAttribute singletonAttribute = SingletonManager.GetFunctionSingletonOrNull(_descriptor, isTriggered: true);
if (singletonAttribute != null)
{
string boundScopeId = _singletonManager.GetBoundScopeId(singletonAttribute.ScopeId, bindingData);
IValueProvider singletonValueProvider = new SingletonValueProvider(_descriptor, boundScopeId, context.FunctionInstanceId.ToString(), singletonAttribute, _singletonManager);
valueProviders.Add(SingletonValueProvider.SingletonParameterName, singletonValueProvider);
}
BindingContext bindingContext = FunctionBinding.NewBindingContext(context, bindingData, parameters);
foreach (KeyValuePair<string, IBinding> item in _nonTriggerBindings)
{
string name = item.Key;
IBinding binding = item.Value;
IValueProvider valueProvider;
try
{
if (parameters != null && parameters.TryGetValue(name, out object parameterValue))
{
valueProvider = await binding.BindAsync(parameterValue, context);
}
else
{
valueProvider = await binding.BindAsync(bindingContext);
}
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception exception)
{
valueProvider = new BindingExceptionValueProvider(name, exception);
}
valueProviders.Add(name, valueProvider);
}
// Triggers can optionally process the return values of functions. They do so by declaring
// a "$return" key in their binding data dictionary and mapping it to an IValueBinder.
// An explicit return binding takes precedence over an implicit trigger binding.
if (!valueProviders.ContainsKey(FunctionIndexer.ReturnParamName))
{
if (triggerReturnValueProvider != null)
{
valueProviders.Add(FunctionIndexer.ReturnParamName, triggerReturnValueProvider);
}
}
return valueProviders;
}
}
}