Skip to content

Commit 76a4914

Browse files
committed
Fixing nested JSON binding bug (#412).
1 parent 98df25e commit 76a4914

File tree

7 files changed

+74
-44
lines changed

7 files changed

+74
-44
lines changed

src/WebJobs.Script/Description/FunctionInvokerBase.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ protected virtual void OnScriptFileChanged(object sender, FileSystemEventArgs e)
9090
/// binding data because all the POCO binders require strong
9191
/// typing
9292
/// </summary>
93-
protected static Dictionary<string, string> GetBindingData(object value, IBinderEx binder)
93+
internal static Dictionary<string, string> GetBindingData(object value, IBinderEx binder)
9494
{
9595
// First apply any existing binding data. Any additional binding
9696
// data coming from the message will take precedence
@@ -108,7 +108,8 @@ protected static Dictionary<string, string> GetBindingData(object value, IBinder
108108
// only includes top level properties)
109109
JObject parsed = JObject.Parse(json);
110110
var additionalBindingData = parsed.Children<JProperty>()
111-
.Where(p => p.Value.Type != JTokenType.Object)
111+
.Where(p => p.Value != null &&
112+
(p.Value.Type != JTokenType.Object & p.Value.Type != JTokenType.Array))
112113
.ToDictionary(p => p.Name, p => (string)p);
113114

114115
if (additionalBindingData != null)

src/WebJobs.Script/Description/PowerShell/PowerShellFunctionInvoker.cs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.Linq;
99
using System.Management.Automation;
1010
using System.Management.Automation.Runspaces;
11-
using System.Net.Http;
1211
using System.Text;
1312
using System.Text.RegularExpressions;
1413
using System.Threading.Tasks;
@@ -59,41 +58,22 @@ public override async Task Invoke(object[] parameters)
5958

6059
try
6160
{
62-
object convertedInput = input;
63-
if (input != null)
64-
{
65-
HttpRequestMessage request = input as HttpRequestMessage;
66-
if (request != null)
67-
{
68-
// TODO: Handle other content types? (E.g. byte[])
69-
if (request.Content != null && request.Content.Headers.ContentLength > 0)
70-
{
71-
convertedInput = ((HttpRequestMessage)input).Content.ReadAsStringAsync().Result;
72-
}
73-
}
74-
}
75-
7661
TraceWriter.Info(string.Format("Function started (Id={0})", invocationId));
7762

78-
string functionInstanceOutputPath = Path.Combine(Path.GetTempPath(), "Functions", "Binding",
79-
invocationId);
80-
63+
object convertedInput = ConvertInput(input);
8164
Dictionary<string, string> bindingData = GetBindingData(convertedInput, binder);
8265
bindingData["InvocationId"] = invocationId;
8366

8467
Dictionary<string, string> environmentVariables = new Dictionary<string, string>();
8568

86-
await
87-
ProcessInputBindingsAsync(convertedInput, functionInstanceOutputPath, binder, _inputBindings, _outputBindings, bindingData,
88-
environmentVariables);
69+
string functionInstanceOutputPath = Path.Combine(Path.GetTempPath(), "Functions", "Binding", invocationId);
70+
await ProcessInputBindingsAsync(convertedInput, functionInstanceOutputPath, binder, _inputBindings, _outputBindings, bindingData, environmentVariables);
8971

90-
InitializeEnvironmentVariables(environmentVariables, functionInstanceOutputPath, input, _outputBindings,
91-
functionExecutionContext);
72+
InitializeEnvironmentVariables(environmentVariables, functionInstanceOutputPath, input, _outputBindings, functionExecutionContext);
9273

9374
PSDataCollection<ErrorRecord> errors = await InvokePowerShellScript(environmentVariables, traceWriter);
9475

95-
await
96-
ProcessOutputBindingsAsync(functionInstanceOutputPath, _outputBindings, input, binder, bindingData);
76+
await ProcessOutputBindingsAsync(functionInstanceOutputPath, _outputBindings, input, binder, bindingData);
9777

9878
if (errors.Any())
9979
{

src/WebJobs.Script/Description/Script/ScriptFunctionInvoker.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.Globalization;
99
using System.IO;
1010
using System.Linq;
11-
using System.Net.Http;
1211
using System.Threading.Tasks;
1312
using Microsoft.Azure.WebJobs.Host;
1413
using Microsoft.Azure.WebJobs.Host.Bindings.Runtime;
@@ -86,21 +85,6 @@ internal async Task ExecuteScriptAsync(string path, string arguments, object[] i
8685
FunctionStartedEvent startedEvent = new FunctionStartedEvent(functionExecutionContext.InvocationId, Metadata);
8786
_metrics.BeginEvent(startedEvent);
8887

89-
// perform any required input conversions
90-
object convertedInput = input;
91-
if (input != null)
92-
{
93-
HttpRequestMessage request = input as HttpRequestMessage;
94-
if (request != null)
95-
{
96-
// TODO: Handle other content types? (E.g. byte[])
97-
if (request.Content != null && request.Content.Headers.ContentLength > 0)
98-
{
99-
convertedInput = ((HttpRequestMessage)input).Content.ReadAsStringAsync().Result;
100-
}
101-
}
102-
}
103-
10488
TraceWriter.Info(string.Format("Function started (Id={0})", invocationId));
10589

10690
string workingDirectory = Path.GetDirectoryName(_scriptFilePath);
@@ -109,6 +93,7 @@ internal async Task ExecuteScriptAsync(string path, string arguments, object[] i
10993
Dictionary<string, string> environmentVariables = new Dictionary<string, string>();
11094
InitializeEnvironmentVariables(environmentVariables, functionInstanceOutputPath, input, _outputBindings, functionExecutionContext);
11195

96+
object convertedInput = ConvertInput(input);
11297
Dictionary<string, string> bindingData = GetBindingData(convertedInput, binder);
11398
bindingData["InvocationId"] = invocationId;
11499

src/WebJobs.Script/Description/ScriptFunctionInvokerBase.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,25 @@ protected static async Task ProcessOutputBindingsAsync(string functionInstanceOu
125125
}
126126
}
127127

128+
protected static object ConvertInput(object input)
129+
{
130+
if (input != null)
131+
{
132+
// perform any required input conversions
133+
HttpRequestMessage request = input as HttpRequestMessage;
134+
if (request != null)
135+
{
136+
// TODO: Handle other content types? (E.g. byte[])
137+
if (request.Content != null && request.Content.Headers.ContentLength > 0)
138+
{
139+
return ((HttpRequestMessage)input).Content.ReadAsStringAsync().Result;
140+
}
141+
}
142+
}
143+
144+
return input;
145+
}
146+
128147
protected void InitializeEnvironmentVariables(Dictionary<string, string> environmentVariables, string functionInstanceOutputPath, object input, Collection<FunctionBinding> outputBindings, ExecutionContext executionContext)
129148
{
130149
environmentVariables["InvocationId"] = executionContext.InvocationId.ToString();

test/WebJobs.Script.Tests/CompositeTraceWriterTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Diagnostics;
55
using System.Linq;
66
using Microsoft.Azure.WebJobs.Host;
7-
using Microsoft.Azure.WebJobs.Script;
87
using Xunit;
98

109
namespace Microsoft.Azure.WebJobs.Script.Tests
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Threading;
8+
using Microsoft.Azure.WebJobs.Host.Bindings;
9+
using Microsoft.Azure.WebJobs.Host.Bindings.Runtime;
10+
using Microsoft.Azure.WebJobs.Script.Description;
11+
using Moq;
12+
using Xunit;
13+
14+
namespace Microsoft.Azure.WebJobs.Script.Tests
15+
{
16+
public class FunctionInvokerBaseTests
17+
{
18+
[Fact]
19+
public void GetBindingData_HandlesNestedJsonPayloads()
20+
{
21+
string input = "{ 'test': 'testing', 'baz': 123, 'nested': [ { 'nesting': 'yes' } ] }";
22+
23+
var binderMock = new Mock<IBinderEx>(MockBehavior.Strict);
24+
25+
var ambientBindingData = new Dictionary<string, object>
26+
{
27+
{ "foo", "Value1" },
28+
{ "bar", "Value2" },
29+
{ "baz", "Value3" }
30+
};
31+
FunctionBindingContext functionContext = new FunctionBindingContext(Guid.NewGuid(), CancellationToken.None, new TestTraceWriter(TraceLevel.Verbose));
32+
AmbientBindingContext bindingContext = new AmbientBindingContext(functionContext, ambientBindingData);
33+
binderMock.SetupGet(p => p.BindingContext).Returns(bindingContext);
34+
35+
var bindingData = FunctionInvokerBase.GetBindingData(input, binderMock.Object);
36+
37+
Assert.Equal("Value1", bindingData["foo"]);
38+
Assert.Equal("Value2", bindingData["bar"]);
39+
Assert.Equal("testing", bindingData["test"]);
40+
41+
// input data overrides ambient data
42+
Assert.Equal("123", bindingData["baz"]);
43+
}
44+
}
45+
}

test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@
282282
<Compile Include="FunctionDescriptorProviderTests.cs" />
283283
<Compile Include="FunctionGeneratorTests.cs" />
284284
<Compile Include="Description\DotNet\CSharp\PackageAssemblyResolverTests.cs" />
285+
<Compile Include="FunctionInvokerBaseTests.cs" />
285286
<Compile Include="MetricsEventManagerTests.cs" />
286287
<Compile Include="PowerShellInvokerTests.cs" />
287288
<Compile Include="Properties\Resources.Designer.cs">

0 commit comments

Comments
 (0)