public override async Task Invoke(object[] parameters)
        {
            object           input       = parameters[0];
            TraceWriter      traceWriter = (TraceWriter)parameters[1];
            Binder           binder      = (Binder)parameters[2];
            ExecutionContext functionExecutionContext = (ExecutionContext)parameters[3];
            string           invocationId             = functionExecutionContext.InvocationId.ToString();

            FunctionStartedEvent startedEvent = new FunctionStartedEvent(functionExecutionContext.InvocationId, Metadata);

            _metrics.BeginEvent(startedEvent);

            try
            {
                TraceWriter.Info(string.Format("Function started (Id={0})", invocationId));

                DataType dataType = _trigger.DataType ?? DataType.String;
                var      scriptExecutionContext = CreateScriptExecutionContext(input, dataType, binder, traceWriter, TraceWriter, functionExecutionContext);
                var      bindingData            = (Dictionary <string, object>)scriptExecutionContext["bindingData"];
                bindingData["InvocationId"] = invocationId;

                await ProcessInputBindingsAsync(binder, scriptExecutionContext, bindingData);

                object functionResult = await ScriptFunc(scriptExecutionContext);

                await ProcessOutputBindingsAsync(_outputBindings, input, binder, bindingData, scriptExecutionContext, functionResult);

                TraceWriter.Info(string.Format("Function completed (Success, Id={0})", invocationId));
            }
            catch
            {
                startedEvent.Success = false;
                TraceWriter.Error(string.Format("Function completed (Failure, Id={0})", invocationId));
                throw;
            }
            finally
            {
                _metrics.EndEvent(startedEvent);
            }
        }
        private async Task ProcessInputBindingsAsync(Binder binder, Dictionary <string, object> executionContext, Dictionary <string, object> bindingData)
        {
            var bindings = (Dictionary <string, object>)executionContext["bindings"];

            // create an ordered array of all inputs and add to
            // the execution context. These will be promoted to
            // positional parameters
            List <object> inputs = new List <object>();

            inputs.Add(bindings[_trigger.Name]);

            var nonTriggerInputBindings = _inputBindings.Where(p => !p.Metadata.IsTrigger);

            foreach (var inputBinding in nonTriggerInputBindings)
            {
                BindingContext bindingContext = new BindingContext
                {
                    Binder      = binder,
                    BindingData = bindingData,
                    DataType    = inputBinding.Metadata.DataType ?? DataType.String
                };
                await inputBinding.BindAsync(bindingContext);

                // Perform any JSON to object conversions if the
                // value is JSON or a JToken
                object value = bindingContext.Value;
                object converted;
                if (TryConvertJson(bindingContext.Value, out converted))
                {
                    value = converted;
                }

                bindings.Add(inputBinding.Metadata.Name, value);
                inputs.Add(value);
            }

            executionContext["inputs"] = inputs;
        }
        private Dictionary <string, object> CreateScriptExecutionContext(object input, DataType dataType, Binder binder, TraceWriter traceWriter, TraceWriter fileTraceWriter, ExecutionContext functionExecutionContext)
        {
            // create a TraceWriter wrapper that can be exposed to Node.js
            var log = (Func <object, Task <object> >)(p =>
            {
                string text = p as string;
                if (text != null)
                {
                    traceWriter.Info(text);
                    fileTraceWriter.Info(text);
                }

                return(Task.FromResult <object>(null));
            });

            var bindings = new Dictionary <string, object>();
            var bind     = (Func <object, Task <object> >)(p =>
            {
                IDictionary <string, object> bindValues = (IDictionary <string, object>)p;
                foreach (var bindValue in bindValues)
                {
                    bindings[bindValue.Key] = bindValue.Value;
                }
                return(Task.FromResult <object>(null));
            });

            var context = new Dictionary <string, object>()
            {
                { "invocationId", functionExecutionContext.InvocationId },
                { "log", log },
                { "bindings", bindings },
                { "bind", bind }
            };

            if (!string.IsNullOrEmpty(_entryPoint))
            {
                context["entryPoint"] = _entryPoint;
            }

            // This is the input value that we will use to extract binding data.
            // Since binding data extraction is based on JSON parsing, in the
            // various conversions below, we set this to the appropriate JSON
            // string when possible.
            object bindDataInput = input;

            if (input is HttpRequestMessage)
            {
                // convert the request to a json object
                HttpRequestMessage request = (HttpRequestMessage)input;
                string             rawBody = null;
                var requestObject          = CreateRequestObject(request, out rawBody);
                input = requestObject;

                if (rawBody != null)
                {
                    requestObject["rawBody"] = rawBody;
                    bindDataInput            = rawBody;
                }

                // If this is a WebHook function, the input should be the
                // request body
                HttpTriggerBindingMetadata httpBinding = _trigger as HttpTriggerBindingMetadata;
                if (httpBinding != null &&
                    !string.IsNullOrEmpty(httpBinding.WebHookType))
                {
                    input = requestObject["body"];

                    // make the entire request object available as well
                    // this is symmetric with context.res which we also support
                    context["req"] = requestObject;
                }
            }
            else if (input is TimerInfo)
            {
                TimerInfo timerInfo   = (TimerInfo)input;
                var       inputValues = new Dictionary <string, object>()
                {
                    { "isPastDue", timerInfo.IsPastDue }
                };
                if (timerInfo.ScheduleStatus != null)
                {
                    inputValues["last"] = timerInfo.ScheduleStatus.Last.ToString("s", CultureInfo.InvariantCulture);
                    inputValues["next"] = timerInfo.ScheduleStatus.Next.ToString("s", CultureInfo.InvariantCulture);
                }
                input = inputValues;
            }
            else if (input is Stream)
            {
                FunctionBinding.ConvertStreamToValue((Stream)input, dataType, ref input);
            }

            ApplyBindingData(bindDataInput, binder);

            // normalize the bindingData object passed into Node
            // we must convert values to types supported by Edge
            // marshalling as needed
            Dictionary <string, object> normalizedBindingData = new Dictionary <string, object>();

            foreach (var pair in binder.BindingData)
            {
                var value = pair.Value;
                if (value != null && !IsEdgeSupportedType(value.GetType()))
                {
                    value = value.ToString();
                }
                normalizedBindingData[pair.Key] = value;
            }
            context["bindingData"] = normalizedBindingData;

            // if the input is json, try converting to an object or array
            object converted;

            if (TryConvertJson(input, out converted))
            {
                input = converted;
            }

            bindings.Add(_trigger.Name, input);

            return(context);
        }
        private static async Task ProcessOutputBindingsAsync(Collection <FunctionBinding> outputBindings, object input, Binder binder,
                                                             Dictionary <string, object> bindingData, Dictionary <string, object> scriptExecutionContext, object functionResult)
        {
            if (outputBindings == null)
            {
                return;
            }

            // if the function returned binding values via the function result,
            // apply them to context.bindings
            var bindings = (Dictionary <string, object>)scriptExecutionContext["bindings"];
            IDictionary <string, object> functionOutputs = functionResult as IDictionary <string, object>;

            if (functionOutputs != null)
            {
                foreach (var output in functionOutputs)
                {
                    bindings[output.Key] = output.Value;
                }
            }

            foreach (FunctionBinding binding in outputBindings)
            {
                // get the output value from the script
                object value = null;
                if (bindings.TryGetValue(binding.Metadata.Name, out value) && value != null)
                {
                    if (value.GetType() == typeof(ExpandoObject) ||
                        (value is Array && value.GetType() != typeof(byte[])))
                    {
                        value = JsonConvert.SerializeObject(value);
                    }

                    BindingContext bindingContext = new BindingContext
                    {
                        TriggerValue = input,
                        Binder       = binder,
                        BindingData  = bindingData,
                        Value        = value
                    };
                    await binding.BindAsync(bindingContext);
                }
            }
        }
Example #5
0
        private static async Task ProcessOutputBindingsAsync(Collection <FunctionBinding> outputBindings, object input, Binder binder,
                                                             Dictionary <string, object> bindingData, Dictionary <string, object> scriptExecutionContext, object functionResult)
        {
            if (outputBindings == null)
            {
                return;
            }

            var bindings           = (Dictionary <string, object>)scriptExecutionContext["bindings"];
            var returnValueBinding = outputBindings.SingleOrDefault(p => p.Metadata.IsReturn);

            if (returnValueBinding != null)
            {
                // if there is a $return binding, bind the entire function return to it
                // if additional output bindings need to be bound, they'll have to use the explicit
                // context.bindings mechanism to set values, not return value.
                bindings[ScriptConstants.SystemReturnParameterBindingName] = functionResult;
            }
            else
            {
                // if the function returned binding values via the function result,
                // apply them to context.bindings
                IDictionary <string, object> functionOutputs = functionResult as IDictionary <string, object>;
                if (functionOutputs != null)
                {
                    foreach (var output in functionOutputs)
                    {
                        bindings[output.Key] = output.Value;
                    }
                }
            }

            foreach (FunctionBinding binding in outputBindings)
            {
                // get the output value from the script
                object value     = null;
                bool   haveValue = bindings.TryGetValue(binding.Metadata.Name, out value);
                if (!haveValue && binding.Metadata.Type == "http")
                {
                    // http bindings support a special context.req/context.res programming
                    // model, so we must map that back to the actual binding name if a value
                    // wasn't provided using the binding name itself
                    haveValue = bindings.TryGetValue("res", out value);
                }

                // apply the value to the binding
                if (haveValue && value != null)
                {
                    value = ConvertBindingValue(value);

                    BindingContext bindingContext = new BindingContext
                    {
                        TriggerValue = input,
                        Binder       = binder,
                        BindingData  = bindingData,
                        Value        = value
                    };
                    await binding.BindAsync(bindingContext);
                }
            }
        }