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); } } }
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); } } }