in src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs [161:350]
internal async Task IndexMethodAsyncCore(MethodInfo method, IFunctionIndexCollector index, CancellationToken cancellationToken)
{
Debug.Assert(method != null);
bool hasNoAutomaticTriggerAttribute = method.GetCustomAttribute<NoAutomaticTriggerAttribute>() != null;
ITriggerBinding triggerBinding = null;
ParameterInfo triggerParameter = null;
IEnumerable<ParameterInfo> parameters = method.GetParameters();
foreach (ParameterInfo parameter in parameters)
{
ITriggerBinding possibleTriggerBinding = await _triggerBindingProvider.TryCreateAsync(new TriggerBindingProviderContext(parameter, cancellationToken));
if (possibleTriggerBinding != null)
{
if (triggerBinding == null)
{
triggerBinding = possibleTriggerBinding;
triggerParameter = parameter;
}
else
{
throw new InvalidOperationException("More than one trigger per function is not allowed.");
}
}
}
Dictionary<string, IBinding> nonTriggerBindings = new Dictionary<string, IBinding>();
IReadOnlyDictionary<string, Type> bindingDataContract;
string sharedListenerId = null;
if (triggerBinding != null)
{
sharedListenerId = GetSharedListenerIdOrNull(triggerBinding);
bindingDataContract = triggerBinding.BindingDataContract;
// See if a regular binding can handle it.
IBinding binding = await _bindingProvider.TryCreateAsync(new BindingProviderContext(triggerParameter, bindingDataContract, cancellationToken));
if (binding != null)
{
triggerBinding = new TriggerWrapper(triggerBinding, binding);
}
}
else
{
bindingDataContract = null;
}
bool hasParameterBindingAttribute = false;
Exception invalidInvokeBindingException = null;
ReturnParameterInfo returnParameter = null;
bool triggerHasReturnBinding = false;
if (TypeUtility.TryGetReturnType(method, out Type methodReturnType) && !IsUnitType(methodReturnType))
{
if (bindingDataContract != null && bindingDataContract.TryGetValue(ReturnParamName, out Type triggerReturnType))
{
// The trigger will handle the return value.
triggerHasReturnBinding = true;
}
// We treat binding to the return type the same as binding to an 'out T' parameter.
// An explicit return binding takes precedence over an implicit trigger binding.
returnParameter = new ReturnParameterInfo(method, methodReturnType);
parameters = parameters.Concat(new ParameterInfo[] { returnParameter });
}
foreach (ParameterInfo parameter in parameters)
{
if (parameter == triggerParameter)
{
continue;
}
IBinding binding = await _bindingProvider.TryCreateAsync(new BindingProviderContext(parameter, bindingDataContract, cancellationToken));
if (binding == null)
{
if (parameter == returnParameter)
{
if (triggerHasReturnBinding)
{
// Ok. Skip and let trigger own the return binding.
continue;
}
else
{
// If the method has a return value, then we must have a binding to it.
// This is similar to the error we used to throw.
invalidInvokeBindingException = new InvalidOperationException("Functions must return Task or void, have a binding attribute for the return value, or be triggered by a binding that natively supports return values.");
}
}
if (triggerBinding != null && !hasNoAutomaticTriggerAttribute)
{
string extendedBindingErrorHelpText = Resource.ExtensionInitializationMessage;
if (bindingDataContract != null)
{
// If a non-trigger/non-attributed parameter binding fails, check to see if we've successfully bound
// a trigger parameter and have a binding contract. If so, it might be that the customer is trying to
// bind to a contract member. If the parameter type matches one of the binding contract members,
// extend the binding error by indicating the valid parameter names for that Type.
string[] matchingContractNames = bindingDataContract.Where(p => parameter.ParameterType == p.Value).Select(p => p.Key).ToArray();
if (matchingContractNames.Length > 0)
{
string bindingContractHelpText = $"The binding supports the following parameter names for type {parameter.ParameterType.Name}: ({string.Join(", ", matchingContractNames)}). If you're trying to bind to one of those values, rename your parameter to match the contract name (case insensitive).";
extendedBindingErrorHelpText = $"{bindingContractHelpText} {extendedBindingErrorHelpText}";
}
}
string bindingError = string.Format(Resource.UnableToBindParameterFormat, parameter.Name, parameter.ParameterType.Name, extendedBindingErrorHelpText);
throw new InvalidOperationException(bindingError);
}
else
{
// Host.Call-only parameter
binding = InvokeBinding.Create(parameter.Name, parameter.ParameterType);
if (binding == null && invalidInvokeBindingException == null)
{
// This function might not have any attribute, in which case we shouldn't throw an
// exception when we can't bind it. Instead, save this exception for later once we determine
// whether or not it is an SDK function.
invalidInvokeBindingException = new InvalidOperationException(
string.Format(Resource.UnableToBindParameterFormat,
parameter.Name, parameter.ParameterType.Name, Resource.ExtensionInitializationMessage));
}
}
}
else if (!hasParameterBindingAttribute)
{
hasParameterBindingAttribute = binding.FromAttribute;
}
nonTriggerBindings.Add(parameter.Name, binding);
}
// Only index functions with some kind of attribute on them. Three ways that could happen:
// 1. There's an attribute on a trigger parameter (all triggers come from attributes).
// 2. There's an attribute on a non-trigger parameter (some non-trigger bindings come from attributes).
if (triggerBinding == null && !hasParameterBindingAttribute)
{
// 3. There's an attribute on the method itself (NoAutomaticTrigger).
if (method.GetCustomAttribute<NoAutomaticTriggerAttribute>() == null)
{
return;
}
}
if (TypeUtility.IsAsyncVoid(method))
{
string msg = $"Function '{Utility.GetFunctionShortName(method)}' is async but does not return a Task. Your function may not run correctly.";
_logger?.LogWarning(msg);
}
if (invalidInvokeBindingException != null)
{
throw invalidInvokeBindingException;
}
// Validation: prevent multiple ConsoleOutputs
if (nonTriggerBindings.OfType<TraceWriterBinding>().Count() > 1)
{
throw new InvalidOperationException("Can't have multiple TraceWriter/TextWriter parameters in a single function.");
}
string triggerParameterName = triggerParameter != null ? triggerParameter.Name : null;
FunctionDescriptor functionDescriptor = CreateFunctionDescriptor(method, triggerParameterName, triggerBinding, nonTriggerBindings, sharedListenerId);
IFunctionInvoker invoker = FunctionInvokerFactory.Create(method, _activator);
IFunctionDefinition functionDefinition;
if (triggerBinding != null)
{
Type triggerValueType = triggerBinding.TriggerValueType;
var methodInfo = typeof(FunctionIndexer).GetMethod("CreateTriggeredFunctionDefinition", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(triggerValueType);
functionDefinition = (FunctionDefinition)methodInfo.Invoke(this, new object[] { triggerBinding, triggerParameterName, functionDescriptor, nonTriggerBindings, invoker });
if (hasNoAutomaticTriggerAttribute && functionDefinition != null)
{
functionDefinition = new FunctionDefinition(functionDescriptor, functionDefinition.InstanceFactory, listenerFactory: null);
}
}
else
{
IFunctionInstanceFactory instanceFactory = new FunctionInstanceFactory(new FunctionBinding(functionDescriptor, nonTriggerBindings, _singletonManager), invoker, functionDescriptor, _instanceServicesFactory);
functionDefinition = new FunctionDefinition(functionDescriptor, instanceFactory, listenerFactory: null);
}
index.Add(functionDefinition, functionDescriptor, method);
}