internal static async Task BindAsyncCollectorAsync <T>(Stream stream, IBinderEx binder, RuntimeBindingContext runtimeContext) { IAsyncCollector <T> collector = await binder.BindAsync <IAsyncCollector <T> >(runtimeContext); // first read the input stream as a collection ICollection <JToken> values = ReadAsCollection(stream); // convert values as necessary and add to the collector foreach (var value in values) { object converted = null; if (typeof(T) == typeof(string)) { converted = value.ToString(); } else if (typeof(T) == typeof(JObject)) { converted = (JObject)value; } else { throw new ArgumentException("Unsupported collection type."); } await collector.AddAsync((T)converted); } }
protected static Dictionary<string, string> GetBindingData(object value, IBinderEx binder, Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings) { Dictionary<string, string> bindingData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // If there are any parameters in the bindings, // get the binding data. In dynamic script cases we need // to parse this POCO data ourselves - it won't be in the existing // binding data because all the POCO binders require strong // typing if (outputBindings.Any(p => p.HasBindingParameters) || inputBindings.Any(p => p.HasBindingParameters)) { // First apply any existing binding data. Any additional binding // data coming from the message will take precedence ApplyAmbientBindingData(binder, bindingData); try { // if the input value is a JSON string, extract additional // binding data from it string json = value as string; if (!string.IsNullOrEmpty(json) && Utility.IsJson(json)) { // parse the object skipping any nested objects (binding data // only includes top level properties) JObject parsed = JObject.Parse(json); var additionalBindingData = parsed.Children<JProperty>() .Where(p => p.Value.Type != JTokenType.Object) .ToDictionary(p => p.Name, p => (string)p); if (additionalBindingData != null) { foreach (var item in additionalBindingData) { if (item.Value != null) { bindingData[item.Key] = item.Value.ToString(); } } } } } catch { // it's not an error if the incoming message isn't JSON // there are cases where there will be output binding parameters // that don't bind to JSON properties } } return bindingData; }
protected static Dictionary <string, string> GetBindingData(object value, IBinderEx binder, Collection <FunctionBinding> inputBindings, Collection <FunctionBinding> outputBindings) { Dictionary <string, string> bindingData = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); // If there are any parameters in the bindings, // get the binding data. In dynamic script cases we need // to parse this POCO data ourselves - it won't be in the existing // binding data because all the POCO binders require strong // typing if (outputBindings.Any(p => p.HasBindingParameters) || inputBindings.Any(p => p.HasBindingParameters)) { // First apply any existing binding data. Any additional binding // data coming from the message will take precedence ApplyAmbientBindingData(binder, bindingData); try { // if the input value is a JSON string, extract additional // binding data from it string json = value as string; if (!string.IsNullOrEmpty(json) && Utility.IsJson(json)) { // parse the object skipping any nested objects (binding data // only includes top level properties) JObject parsed = JObject.Parse(json); var additionalBindingData = parsed.Children <JProperty>() .Where(p => p.Value.Type != JTokenType.Object) .ToDictionary(p => p.Name, p => (string)p); if (additionalBindingData != null) { foreach (var item in additionalBindingData) { if (item.Value != null) { bindingData[item.Key] = item.Value.ToString(); } } } } } catch { // it's not an error if the incoming message isn't JSON // there are cases where there will be output binding parameters // that don't bind to JSON properties } } return(bindingData); }
internal static async Task BindStreamAsync(Stream stream, FileAccess access, IBinderEx binder, RuntimeBindingContext runtimeContext) { Stream boundStream = await binder.BindAsync <Stream>(runtimeContext); if (access == FileAccess.Write) { await stream.CopyToAsync(boundStream); } else { await boundStream.CopyToAsync(stream); } }
/// <summary> /// We need to merge the ambient binding data that already exists in the IBinder /// with our binding data. We have to do this rather than relying solely on /// IBinder.BindAsync because we need to include any POCO values we get from parsing /// JSON bodies, etc. /// TEMP: We might find a better way to do this in the future, perhaps via core /// SDK changes. /// </summary> protected static void ApplyAmbientBindingData(IBinderEx binder, IDictionary <string, string> bindingData) { var ambientBindingData = binder.BindingContext.BindingData; if (ambientBindingData != null) { // apply the binding data to ours foreach (var item in ambientBindingData) { if (item.Value != null) { bindingData[item.Key] = item.Value.ToString(); } } } }
/// <summary> /// Get the binding data. In dynamic script cases we need /// to parse this POCO data ourselves - it won't be in the existing /// binding data because all the POCO binders require strong /// typing /// </summary> protected static Dictionary <string, string> GetBindingData(object value, IBinderEx binder) { // First apply any existing binding data. Any additional binding // data coming from the message will take precedence Dictionary <string, string> bindingData = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); ApplyAmbientBindingData(binder, bindingData); try { // if the input value is a JSON string, extract additional // binding data from it string json = value as string; if (!string.IsNullOrEmpty(json) && Utility.IsJson(json)) { // parse the object skipping any nested objects (binding data // only includes top level properties) JObject parsed = JObject.Parse(json); var additionalBindingData = parsed.Children <JProperty>() .Where(p => p.Value.Type != JTokenType.Object) .ToDictionary(p => p.Name, p => (string)p); if (additionalBindingData != null) { foreach (var item in additionalBindingData) { if (item.Value != null) { bindingData[item.Key] = item.Value.ToString(); } } } } } catch { // it's not an error if the incoming message isn't JSON // there are cases where there will be output binding parameters // that don't bind to JSON properties } return(bindingData); }
public override async Task Invoke(object[] parameters) { object input = parameters[0]; TraceWriter traceWriter = (TraceWriter)parameters[1]; IBinderEx binder = (IBinderEx)parameters[2]; ExecutionContext functionExecutionContext = (ExecutionContext)parameters[3]; string invocationId = functionExecutionContext.InvocationId.ToString(); FunctionStartedEvent startedEvent = new FunctionStartedEvent(functionExecutionContext.InvocationId, Metadata); _metrics.BeginEvent(startedEvent); try { TraceWriter.Verbose(string.Format("Function started (Id={0})", invocationId)); var scriptExecutionContext = CreateScriptExecutionContext(input, traceWriter, TraceWriter, functionExecutionContext); Dictionary <string, string> bindingData = GetBindingData(input, binder, _inputBindings, _outputBindings); bindingData["InvocationId"] = invocationId; scriptExecutionContext["bindingData"] = bindingData; await ProcessInputBindingsAsync(binder, scriptExecutionContext, bindingData); object functionResult = await ScriptFunc(scriptExecutionContext); await ProcessOutputBindingsAsync(_outputBindings, input, binder, bindingData, scriptExecutionContext, functionResult); TraceWriter.Verbose(string.Format("Function completed (Success, Id={0})", invocationId)); } catch { startedEvent.Success = false; TraceWriter.Verbose(string.Format("Function completed (Failure, Id={0})", invocationId)); throw; } finally { _metrics.EndEvent(startedEvent); } }
private async Task ProcessInputBindingsAsync(IBinderEx binder, Dictionary <string, object> executionContext, Dictionary <string, string> 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) { string stringValue = null; using (MemoryStream stream = new MemoryStream()) { BindingContext bindingContext = new BindingContext { Binder = binder, BindingData = bindingData, Value = stream }; await inputBinding.BindAsync(bindingContext); stream.Seek(0, SeekOrigin.Begin); StreamReader sr = new StreamReader(stream); stringValue = sr.ReadToEnd(); } // if the input is json, try converting to an object object convertedValue = stringValue; convertedValue = TryConvertJsonToObject(stringValue); bindings.Add(inputBinding.Metadata.Name, convertedValue); inputs.Add(convertedValue); } executionContext["inputs"] = inputs; }
private static async Task ProcessOutputBindingsAsync(string functionInstanceOutputPath, Collection <FunctionBinding> outputBindings, object input, IBinderEx binder, Dictionary <string, string> bindingData) { if (outputBindings == null) { return; } try { foreach (var outputBinding in outputBindings) { string filePath = System.IO.Path.Combine(functionInstanceOutputPath, outputBinding.Metadata.Name); if (File.Exists(filePath)) { using (FileStream stream = File.OpenRead(filePath)) { BindingContext bindingContext = new BindingContext { TriggerValue = input, Binder = binder, BindingData = bindingData, Value = stream }; await outputBinding.BindAsync(bindingContext); } } } } finally { // clean up the output directory if (outputBindings.Any() && Directory.Exists(functionInstanceOutputPath)) { Directory.Delete(functionInstanceOutputPath, recursive: true); } } }
private async Task ProcessInputBindingsAsync(IBinderEx binder, Dictionary <string, object> executionContext, Dictionary <string, string> 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 static async Task ProcessOutputBindingsAsync(Collection <FunctionBinding> outputBindings, object input, IBinderEx binder, Dictionary <string, string> 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); } } }
internal async Task ExecuteScriptAsync(string path, string arguments, object[] invocationParameters) { object input = invocationParameters[0]; TraceWriter traceWriter = (TraceWriter)invocationParameters[1]; IBinderEx binder = (IBinderEx)invocationParameters[2]; ExecutionContext functionExecutionContext = (ExecutionContext)invocationParameters[3]; string invocationId = functionExecutionContext.InvocationId.ToString(); FunctionStartedEvent startedEvent = new FunctionStartedEvent(functionExecutionContext.InvocationId, Metadata); _metrics.BeginEvent(startedEvent); // perform any required input conversions object convertedInput = input; if (input != null) { HttpRequestMessage request = input as HttpRequestMessage; if (request != null) { // TODO: Handle other content types? (E.g. byte[]) if (request.Content != null && request.Content.Headers.ContentLength > 0) { convertedInput = ((HttpRequestMessage)input).Content.ReadAsStringAsync().Result; } } } TraceWriter.Info(string.Format("Function started (Id={0})", invocationId)); string workingDirectory = Path.GetDirectoryName(_scriptFilePath); string functionInstanceOutputPath = Path.Combine(Path.GetTempPath(), "Functions", "Binding", invocationId); Dictionary <string, string> environmentVariables = new Dictionary <string, string>(); InitializeEnvironmentVariables(environmentVariables, functionInstanceOutputPath, input, _outputBindings, functionExecutionContext); Dictionary <string, string> bindingData = GetBindingData(convertedInput, binder); bindingData["InvocationId"] = invocationId; await ProcessInputBindingsAsync(convertedInput, functionInstanceOutputPath, binder, bindingData, environmentVariables); // TODO // - put a timeout on how long we wait? // - need to periodically flush the standard out to the TraceWriter Process process = CreateProcess(path, workingDirectory, arguments, environmentVariables); process.Start(); process.WaitForExit(); bool failed = process.ExitCode != 0; startedEvent.Success = !failed; _metrics.EndEvent(startedEvent); if (failed) { startedEvent.Success = false; TraceWriter.Error(string.Format("Function completed (Failure, Id={0})", invocationId)); string error = process.StandardError.ReadToEnd(); throw new ApplicationException(error); } string output = process.StandardOutput.ReadToEnd(); TraceWriter.Info(output); traceWriter.Info(output); await ProcessOutputBindingsAsync(functionInstanceOutputPath, _outputBindings, input, binder, bindingData); TraceWriter.Info(string.Format("Function completed (Success, Id={0})", invocationId)); }
private async Task ProcessInputBindingsAsync(object input, string functionInstanceOutputPath, IBinderEx binder, Dictionary <string, string> bindingData, Dictionary <string, string> environmentVariables) { // if there are any input or output bindings declared, set up the temporary // output directory if (_outputBindings.Count > 0 || _inputBindings.Any()) { Directory.CreateDirectory(functionInstanceOutputPath); } // process input bindings foreach (var inputBinding in _inputBindings) { string filePath = System.IO.Path.Combine(functionInstanceOutputPath, inputBinding.Metadata.Name); using (FileStream stream = File.OpenWrite(filePath)) { // If this is the trigger input, write it directly to the stream. // The trigger binding is a special case because it is early bound // rather than late bound as is the case with all the other input // bindings. if (inputBinding.Metadata.IsTrigger) { if (input is string) { using (StreamWriter sw = new StreamWriter(stream)) { await sw.WriteAsync((string)input); } } else if (input is byte[]) { byte[] bytes = input as byte[]; await stream.WriteAsync(bytes, 0, bytes.Length); } else if (input is Stream) { Stream inputStream = input as Stream; await inputStream.CopyToAsync(stream); } } else { // invoke the input binding BindingContext bindingContext = new BindingContext { Binder = binder, BindingData = bindingData, DataType = DataType.Stream, Value = stream }; await inputBinding.BindAsync(bindingContext); } } environmentVariables[inputBinding.Metadata.Name] = Path.Combine(functionInstanceOutputPath, inputBinding.Metadata.Name); } }
private static async Task ProcessOutputBindingsAsync(Collection <FunctionBinding> outputBindings, object input, IBinderEx binder, Dictionary <string, string> 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 // we support primatives (int, string, object) as well as arrays of these object value = null; if (bindings.TryGetValue(binding.Metadata.Name, out value)) { // we only support strings, objects, or arrays of those (not primitive types like int) if (value.GetType() == typeof(ExpandoObject) || value is Array) { value = JsonConvert.SerializeObject(value); } if (!(value is string)) { throw new InvalidOperationException(string.Format("Invalid value specified for binding '{0}'", binding.Metadata.Name)); } byte[] bytes = Encoding.UTF8.GetBytes((string)value); using (MemoryStream ms = new MemoryStream(bytes)) { BindingContext bindingContext = new BindingContext { Input = input, Binder = binder, BindingData = bindingData, Value = ms }; await binding.BindAsync(bindingContext); } } } }
private async Task ProcessInputBindingsAsync(object input, string functionInstanceOutputPath, IBinderEx binder, Dictionary<string, string> bindingData, Dictionary<string, string> environmentVariables) { // if there are any input or output bindings declared, set up the temporary // output directory if (_outputBindings.Count > 0 || _inputBindings.Any()) { Directory.CreateDirectory(functionInstanceOutputPath); } // process input bindings foreach (var inputBinding in _inputBindings) { string filePath = System.IO.Path.Combine(functionInstanceOutputPath, inputBinding.Metadata.Name); using (FileStream stream = File.OpenWrite(filePath)) { // If this is the trigger input, write it directly to the stream. // The trigger binding is a special case because it is early bound // rather than late bound as is the case with all the other input // bindings. if (inputBinding.Metadata.IsTrigger) { if (input is string) { using (StreamWriter sw = new StreamWriter(stream)) { await sw.WriteAsync((string)input); } } else if (input is byte[]) { byte[] bytes = input as byte[]; await stream.WriteAsync(bytes, 0, bytes.Length); } else if (input is Stream) { Stream inputStream = input as Stream; await inputStream.CopyToAsync(stream); } } else { // invoke the input binding BindingContext bindingContext = new BindingContext { Binder = binder, BindingData = bindingData, Value = stream }; await inputBinding.BindAsync(bindingContext); } } environmentVariables[inputBinding.Metadata.Name] = Path.Combine(functionInstanceOutputPath, inputBinding.Metadata.Name); } }
private static async Task ProcessOutputBindingsAsync(string functionInstanceOutputPath, Collection<FunctionBinding> outputBindings, object input, IBinderEx binder, Dictionary<string, string> bindingData) { if (outputBindings == null) { return; } try { foreach (var outputBinding in outputBindings) { string filePath = System.IO.Path.Combine(functionInstanceOutputPath, outputBinding.Metadata.Name); if (File.Exists(filePath)) { using (FileStream stream = File.OpenRead(filePath)) { BindingContext bindingContext = new BindingContext { Input = input, Binder = binder, BindingData = bindingData, Value = stream }; await outputBinding.BindAsync(bindingContext); } } } } finally { // clean up the output directory if (outputBindings.Any() && Directory.Exists(functionInstanceOutputPath)) { Directory.Delete(functionInstanceOutputPath, recursive: true); } } }
private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding> outputBindings, object input, IBinderEx binder, Dictionary<string, string> 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 // we support primatives (int, string, object) as well as arrays of these object value = null; if (bindings.TryGetValue(binding.Metadata.Name, out value)) { // we only support strings, objects, or arrays of those (not primitive types like int) if (value.GetType() == typeof(ExpandoObject) || value is Array) { value = JsonConvert.SerializeObject(value); } if (!(value is string)) { throw new InvalidOperationException(string.Format("Invalid value specified for binding '{0}'", binding.Metadata.Name)); } byte[] bytes = Encoding.UTF8.GetBytes((string)value); using (MemoryStream ms = new MemoryStream(bytes)) { BindingContext bindingContext = new BindingContext { Input = input, Binder = binder, BindingData = bindingData, Value = ms }; await binding.BindAsync(bindingContext); } } } }
private async Task ProcessInputBindingsAsync(IBinderEx binder, Dictionary<string, object> executionContext, Dictionary<string, string> 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) { string stringValue = null; using (MemoryStream stream = new MemoryStream()) { BindingContext bindingContext = new BindingContext { Binder = binder, BindingData = bindingData, Value = stream }; await inputBinding.BindAsync(bindingContext); stream.Seek(0, SeekOrigin.Begin); StreamReader sr = new StreamReader(stream); stringValue = sr.ReadToEnd(); } // if the input is json, try converting to an object object convertedValue = stringValue; convertedValue = TryConvertJsonToObject(stringValue); bindings.Add(inputBinding.Metadata.Name, convertedValue); inputs.Add(convertedValue); } executionContext["inputs"] = inputs; }
private Dictionary <string, object> CreateScriptExecutionContext(object input, DataType dataType, IBinderEx 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 } }; // 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); } context["bindingData"] = GetBindingData(bindDataInput, binder); // 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); }
/// <summary> /// We need to merge the ambient binding data that already exists in the IBinder /// with our binding data. We have to do this rather than relying solely on /// IBinder.BindAsync because we need to include any POCO values we get from parsing /// JSON bodies, etc. /// TEMP: We might find a better way to do this in the future, perhaps via core /// SDK changes. /// </summary> protected static void ApplyAmbientBindingData(IBinderEx binder, IDictionary<string, string> bindingData) { var ambientBindingData = binder.BindingContext.BindingData; if (ambientBindingData != null) { // apply the binding data to ours foreach (var item in ambientBindingData) { if (item.Value != null) { bindingData[item.Key] = item.Value.ToString(); } } } }