Skip to content

Commit df9657f

Browse files
committed
Fixing ExpandoObject response serialization
1 parent b55f19b commit df9657f

File tree

2 files changed

+101
-17
lines changed

2 files changed

+101
-17
lines changed

src/WebJobs.Script/Binding/HttpBinding.cs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ namespace Microsoft.Azure.WebJobs.Script.Binding
2222
{
2323
public class HttpBinding : FunctionBinding, IResultProcessingBinding
2424
{
25-
private static readonly DictionaryJsonConverter _dictionaryJsonConverter = new DictionaryJsonConverter();
26-
2725
public HttpBinding(ScriptHostConfiguration config, BindingMetadata metadata, FileAccess access) :
2826
base(config, metadata, access)
2927
{
@@ -68,17 +66,16 @@ internal static HttpResponseMessage CreateResponse(HttpRequestMessage request, o
6866
}
6967
}
7068

71-
// see if the content is a response object, defining http response
72-
// properties
69+
// see if the content is a response object, defining http response properties
7370
IDictionary<string, object> responseObject = null;
7471
if (content is JObject)
7572
{
76-
responseObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(stringContent, _dictionaryJsonConverter);
73+
responseObject = JsonConvert.DeserializeObject<ExpandoObject>(stringContent);
7774
}
7875
else
7976
{
8077
// Handle ExpandoObjects
81-
responseObject = content as IDictionary<string, object>;
78+
responseObject = content as ExpandoObject;
8279
}
8380

8481
HttpStatusCode statusCode = HttpStatusCode.OK;
@@ -155,9 +152,7 @@ private static HttpResponseMessage CreateResponse(HttpRequestMessage request, Ht
155152
(headers?.TryGetValue<string>("content-type", out contentType, ignoreCase: true) ?? false) &&
156153
MediaTypeHeaderValue.TryParse((string)contentType, out mediaType))
157154
{
158-
MediaTypeFormatter writer = request.GetConfiguration()
159-
.Formatters.FindWriter(content.GetType(), mediaType);
160-
155+
var writer = request.GetConfiguration().Formatters.FindWriter(content.GetType(), mediaType);
161156
if (writer != null)
162157
{
163158
return new HttpResponseMessage(statusCode)
@@ -166,15 +161,12 @@ private static HttpResponseMessage CreateResponse(HttpRequestMessage request, Ht
166161
};
167162
}
168163

164+
// create a non-negotiated result content
169165
HttpContent resultContent = CreateResultContent(content, mediaType.MediaType);
170-
171-
if (resultContent != null)
166+
return new HttpResponseMessage(statusCode)
172167
{
173-
return new HttpResponseMessage(statusCode)
174-
{
175-
Content = resultContent
176-
};
177-
}
168+
Content = resultContent
169+
};
178170
}
179171

180172
return CreateNegotiatedResponse(request, statusCode, content);
@@ -194,7 +186,7 @@ internal static HttpContent CreateResultContent(object content, string mediaType
194186
string stringContent;
195187
if (content is ExpandoObject)
196188
{
197-
stringContent = Utility.ToJson((ExpandoObject)content);
189+
stringContent = Utility.ToJson((ExpandoObject)content, Formatting.None);
198190
}
199191
else
200192
{
@@ -217,6 +209,7 @@ private static HttpResponseMessage CreateNegotiatedResponse(HttpRequestMessage r
217209
IContentNegotiator negotiator = configuration.Services.GetContentNegotiator();
218210
var negotiationResult = negotiator.Negotiate(content.GetType(), request, configuration.Formatters);
219211

212+
// ObjectContent can handle ExpandoObjects as well
220213
result.Content = new ObjectContent(content.GetType(), content, negotiationResult.Formatter, negotiationResult.MediaType);
221214

222215
return result;

test/WebJobs.Script.Tests/Binding/HttpBindingTests.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
using System.Net.Http;
99
using System.Text;
1010
using System.Threading.Tasks;
11+
using System.Web.Http;
1112
using Microsoft.Azure.WebJobs.Script.Binding;
13+
using Microsoft.Azure.WebJobs.Script.WebHost;
14+
using Newtonsoft.Json;
1215
using Newtonsoft.Json.Linq;
1316
using Xunit;
1417

@@ -121,5 +124,93 @@ public async Task CreateResultContent_ExpandoObject_ReturnsJsonStringContent()
121124
Assert.Equal("Seattle", parsed["location"]);
122125
Assert.Equal("application/json", stringContent.Headers.ContentType.MediaType);
123126
}
127+
128+
[Fact]
129+
public async Task CreateResponse_JsonString_ReturnsExpectedResult()
130+
{
131+
HttpConfiguration config = new HttpConfiguration();
132+
config.Formatters.Add(new PlaintextMediaTypeFormatter());
133+
134+
JObject child = new JObject
135+
{
136+
{ "Name", "Mary" },
137+
{ "Location", "Seattle" },
138+
{ "Age", 5 }
139+
};
140+
141+
JObject parent = new JObject
142+
{
143+
{ "Name", "Bob" },
144+
{ "Location", "Seattle" },
145+
{ "Age", 40 },
146+
{ "Children", new JArray(child) }
147+
};
148+
string expectedBodyJson = parent.ToString(Formatting.None);
149+
150+
// explicitly set a content type that there is no default
151+
// formatter for to force default non-negotiated content codepath
152+
JObject headers = new JObject
153+
{
154+
{ "Content-Type", "foo/bar" }
155+
};
156+
JObject responseObject = new JObject
157+
{
158+
{ "Body", parent },
159+
{ "Headers", headers }
160+
};
161+
HttpRequestMessage request = new HttpRequestMessage();
162+
request.SetConfiguration(config);
163+
var response = HttpBinding.CreateResponse(request, responseObject.ToString());
164+
string resultJson = await response.Content.ReadAsStringAsync();
165+
Assert.Equal(expectedBodyJson, resultJson);
166+
Assert.Equal("foo/bar", response.Content.Headers.ContentType.MediaType);
167+
168+
// Test again with a recognized content-type header, to force content negotiation
169+
headers = new JObject
170+
{
171+
{ "Content-Type", "application/json" }
172+
};
173+
responseObject = new JObject
174+
{
175+
{ "Body", parent },
176+
{ "Headers", headers }
177+
};
178+
request = new HttpRequestMessage();
179+
request.SetConfiguration(config);
180+
response = HttpBinding.CreateResponse(request, responseObject.ToString());
181+
resultJson = await response.Content.ReadAsStringAsync();
182+
Assert.Equal(expectedBodyJson, resultJson);
183+
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
184+
185+
// Test again with an explicitly specified response content type
186+
headers = new JObject
187+
{
188+
{ "Content-Type", "text/plain" }
189+
};
190+
responseObject = new JObject
191+
{
192+
{ "Body", parent },
193+
{ "Headers", headers }
194+
};
195+
request = new HttpRequestMessage();
196+
request.SetConfiguration(config);
197+
response = HttpBinding.CreateResponse(request, responseObject.ToString());
198+
resultJson = await response.Content.ReadAsStringAsync();
199+
Assert.Equal(expectedBodyJson, resultJson);
200+
Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType);
201+
202+
// Test again without an explicit response content type
203+
// to trigger ObjectContent negotiation codepath
204+
responseObject = new JObject
205+
{
206+
{ "Body", parent }
207+
};
208+
request = new HttpRequestMessage();
209+
request.SetConfiguration(config);
210+
response = HttpBinding.CreateResponse(request, responseObject.ToString());
211+
resultJson = await response.Content.ReadAsStringAsync();
212+
Assert.Equal(expectedBodyJson, resultJson);
213+
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
214+
}
124215
}
125216
}

0 commit comments

Comments
 (0)