Skip to content

Commit eb9a41e

Browse files
committed
Enhancing HTTP processing and supported binding types (C#)
1 parent b6493f8 commit eb9a41e

File tree

18 files changed

+198
-46
lines changed

18 files changed

+198
-46
lines changed

WebJobs.Script.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 14
4-
VisualStudioVersion = 14.0.24720.0
4+
VisualStudioVersion = 14.0.25123.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebJobs.Script", "src\WebJobs.Script\WebJobs.Script.csproj", "{1DC670CD-F42F-4D8F-97BD-0E1AA8221094}"
77
EndProject
@@ -232,6 +232,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiHubTrigger", "ApiHubTrig
232232
sample\ApiHubTrigger\index.js = sample\ApiHubTrigger\index.js
233233
EndProjectSection
234234
EndProject
235+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpTriggerWithObject-CSharp", "HttpTriggerWithObject-CSharp", "{75DF7C02-CE61-4D1F-83F9-BFBD38AAAF0E}"
236+
ProjectSection(SolutionItems) = preProject
237+
sample\HttpTriggerWithObject-CSharp\function.json = sample\HttpTriggerWithObject-CSharp\function.json
238+
sample\HttpTriggerWithObject-CSharp\run.csx = sample\HttpTriggerWithObject-CSharp\run.csx
239+
EndProjectSection
240+
EndProject
235241
Global
236242
GlobalSection(SolutionConfigurationPlatforms) = preSolution
237243
Debug|Any CPU = Debug|Any CPU
@@ -302,5 +308,6 @@ Global
302308
{A308340E-16AC-47DA-9402-A279F681D5F3} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
303309
{6F2A5DC3-EE0F-476F-A79B-31D4ED8C7DEB} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
304310
{5FBF4581-318C-4320-81AA-6B2F02AF34EC} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
311+
{75DF7C02-CE61-4D1F-83F9-BFBD38AAAF0E} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
305312
EndGlobalSection
306313
EndGlobal
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"bindings": [
3+
{
4+
"type": "httpTrigger",
5+
"direction": "in",
6+
"methods": [ "post" ]
7+
},
8+
{
9+
"type": "http",
10+
"direction": "out"
11+
}
12+
]
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Net;
3+
4+
public static TestObject Run(TestObject req)
5+
{
6+
req.Greeting = $"Hello, {req.SenderName}";
7+
8+
return req;
9+
}
10+
11+
public class TestObject
12+
{
13+
public string SenderName { get; set; }
14+
15+
public string Greeting { get; set; }
16+
}

sample/WebHook-Generic-CSharp/function.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
{
44
"type": "httpTrigger",
55
"direction": "in",
6-
"webHookType": "genericJson"
6+
"webHookType": "genericJson",
7+
"name": "payload"
78
},
89
{
910
"type": "http",

sample/WebHook-Generic-CSharp/run.csx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@
22

33
using System;
44
using System.Net;
5-
using System.Threading.Tasks;
65
using Newtonsoft.Json;
76
using Newtonsoft.Json.Linq;
87

9-
public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
8+
public static JObject Run(Payload payload)
109
{
11-
string json = await req.Content.ReadAsStringAsync();
12-
Payload payload = JsonConvert.DeserializeObject<Payload>(json);
13-
1410
JObject body = new JObject()
1511
{
1612
{ "result", $"Value: {payload.Value}" }
1713
};
18-
return req.CreateResponse(HttpStatusCode.OK, body, "application/json");
14+
return body;
1915
}
2016

2117
public class Payload

src/WebJobs.Script.WebHost/WebHooks/WebHookReceiverManager.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.AspNet.WebHooks;
1515
using Microsoft.AspNet.WebHooks.Config;
1616
using Microsoft.Azure.WebJobs.Host;
17+
using Microsoft.Azure.WebJobs.Script;
1718
using Microsoft.Azure.WebJobs.Script.Description;
1819

1920
namespace WebJobs.Script.WebHost.WebHooks
@@ -25,7 +26,7 @@ namespace WebJobs.Script.WebHost.WebHooks
2526
public class WebHookReceiverManager : IDisposable
2627
{
2728
internal const string AzureFunctionsCallbackKey = "MS_AzureFunctionsCallback";
28-
29+
2930
private readonly Dictionary<string, IWebHookReceiver> _receiverLookup;
3031
private HttpConfiguration _httpConfiguration;
3132
private bool disposedValue = false;
@@ -120,6 +121,8 @@ public override async Task ExecuteAsync(string receiver, WebHookHandlerContext c
120121
var requestHandler = (Func<HttpRequestMessage, Task<HttpResponseMessage>>)
121122
context.Request.Properties[AzureFunctionsCallbackKey];
122123

124+
context.Request.Properties.Add(ScriptConstants.AzureFunctionsWebHookContextKey, context);
125+
123126
// Invoke the function
124127
context.Response = await requestHandler(context.Request);
125128
}

src/WebJobs.Script.WebHost/WebScriptHostManager.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
using System.Linq;
77
using System.Net;
88
using System.Net.Http;
9+
using System.Reflection;
910
using System.Threading;
1011
using System.Threading.Tasks;
12+
using Microsoft.AspNet.WebHooks;
1113
using Microsoft.Azure.WebJobs;
1214
using Microsoft.Azure.WebJobs.Script;
1315
using Microsoft.Azure.WebJobs.Script.Description;
@@ -18,6 +20,7 @@ namespace WebJobs.Script.WebHost
1820
{
1921
public class WebScriptHostManager : ScriptHostManager
2022
{
23+
private static Lazy<MethodInfo> _getWebHookDataMethod = new Lazy<MethodInfo>(CreateGetWebHookDataMethodInfo);
2124
private IMetricsLogger _metricsLogger;
2225

2326
public WebScriptHostManager(ScriptHostConfiguration config) : base(config)
@@ -32,12 +35,7 @@ public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor fun
3235
// All authentication is assumed to have been done on the request
3336
// BEFORE this method is called
3437

35-
// Invoke the function
36-
ParameterDescriptor triggerParameter = function.Parameters.First(p => p.IsTrigger);
37-
Dictionary<string, object> arguments = new Dictionary<string, object>
38-
{
39-
{ triggerParameter.Name, request }
40-
};
38+
Dictionary<string, object> arguments = await GetFunctionArgumentsAsync(function, request);
4139

4240
// Suspend the current synchronization context so we don't pass the ASP.NET
4341
// context down to the function.
@@ -57,6 +55,53 @@ public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor fun
5755
return response;
5856
}
5957

58+
private static MethodInfo CreateGetWebHookDataMethodInfo()
59+
{
60+
return typeof(WebHookHandlerContextExtensions).GetMethod("GetDataOrDefault", BindingFlags.Public | BindingFlags.Static);
61+
}
62+
63+
private static async Task<Dictionary<string, object>> GetFunctionArgumentsAsync(FunctionDescriptor function, HttpRequestMessage request)
64+
{
65+
ParameterDescriptor triggerParameter = function.Parameters.First(p => p.IsTrigger);
66+
Dictionary<string, object> arguments = new Dictionary<string, object>();
67+
object triggerArgument = null;
68+
if (triggerParameter.Type == typeof(HttpRequestMessage))
69+
{
70+
triggerArgument = request;
71+
}
72+
else
73+
{
74+
// We'll replace the trigger argument but still want to flow the request
75+
// so add it to the arguments, as a system argument
76+
arguments.Add(ScriptConstants.DefaultSystemTriggerParameterName, request);
77+
78+
HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => p.Type == BindingType.HttpTrigger);
79+
if (!string.IsNullOrEmpty(httpFunctionMetadata.WebHookType))
80+
{
81+
WebHookHandlerContext webHookContext;
82+
if (request.Properties.TryGetValue(ScriptConstants.AzureFunctionsWebHookContextKey, out webHookContext))
83+
{
84+
triggerArgument = GetWebHookData(triggerParameter.Type, webHookContext);
85+
}
86+
}
87+
88+
if (triggerArgument == null)
89+
{
90+
triggerArgument = await request.Content.ReadAsAsync(triggerParameter.Type);
91+
}
92+
}
93+
94+
arguments.Add(triggerParameter.Name, triggerArgument);
95+
96+
return arguments;
97+
}
98+
99+
private static object GetWebHookData(Type dataType, WebHookHandlerContext context)
100+
{
101+
MethodInfo getDataMethod = _getWebHookDataMethod.Value.MakeGenericMethod(dataType);
102+
return getDataMethod.Invoke(null, new object[] { context });
103+
}
104+
60105
public FunctionDescriptor GetHttpFunctionOrNull(Uri uri)
61106
{
62107
if (uri == null)

src/WebJobs.Script/Binding/HttpBinding.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
using System.Collections.Generic;
66
using System.Collections.ObjectModel;
77
using System.IO;
8+
using System.Linq;
89
using System.Net;
910
using System.Net.Http;
11+
using System.Net.Http.Formatting;
1012
using System.Net.Http.Headers;
1113
using System.Reflection.Emit;
1214
using System.Threading.Tasks;
@@ -18,8 +20,6 @@ namespace Microsoft.Azure.WebJobs.Script.Binding
1820
{
1921
public class HttpBinding : FunctionBinding, IResultProcessingBinding
2022
{
21-
internal const string HttpResponsePropertyKey = "MS_AzureFunctionsHttpResponse";
22-
2323
public HttpBinding(ScriptHostConfiguration config, BindingMetadata metadata, FileAccess access) :
2424
base(config, metadata, access)
2525
{
@@ -107,22 +107,41 @@ public override async Task BindAsync(BindingContext context)
107107
};
108108
}
109109

110-
request.Properties[HttpResponsePropertyKey] = response;
110+
request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey] = response;
111111
}
112-
113-
public void ProcessResult(object inputValue, object result)
112+
113+
public void ProcessResult(IDictionary<string, object> functionArguments, object[] systemArguments, string triggerInputName, object result)
114114
{
115-
HttpRequestMessage request = inputValue as HttpRequestMessage;
115+
if (result == null)
116+
{
117+
return;
118+
}
119+
120+
HttpRequestMessage request;
116121

117-
if (request != null && result is HttpResponseMessage)
122+
if (!functionArguments.TryGetValue(triggerInputName, out request))
118123
{
119-
request.Properties[HttpResponsePropertyKey] = result;
124+
// No argument is bound to the request message, so we should have
125+
// it in the system arguments
126+
request = systemArguments.FirstOrDefault(a => a is HttpRequestMessage) as HttpRequestMessage;
127+
}
128+
129+
if (request != null)
130+
{
131+
HttpResponseMessage response = result as HttpResponseMessage;
132+
if (response == null)
133+
{
134+
response = request.CreateResponse(HttpStatusCode.OK);
135+
response.Content = new ObjectContent(result.GetType(), result, new JsonMediaTypeFormatter());
136+
}
137+
138+
request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey] = response;
120139
}
121140
}
122141

123142
public bool CanProcessResult(object result)
124143
{
125-
return result is HttpResponseMessage;
144+
return result != null;
126145
}
127146

128147
private static void AddResponseHeader(HttpResponseMessage response, KeyValuePair<string, JToken> header)

src/WebJobs.Script/Binding/IResultProcessingBinding.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System.Collections.Generic;
5+
46
namespace Microsoft.Azure.WebJobs.Script.Binding
57
{
68
/// <summary>
79
/// Represents a binding that may process function execution results.
810
/// </summary>
911
public interface IResultProcessingBinding
1012
{
11-
void ProcessResult(object inputValue, object result);
13+
void ProcessResult(IDictionary<string, object> functionArguments, object[] systemArguments, string triggerInputName, object result);
1214

1315
bool CanProcessResult(object result);
1416
}

src/WebJobs.Script/Description/CSharp/CSharpFunctionDescriptionProvider.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.ObjectModel;
77
using System.Globalization;
88
using System.Linq;
9+
using System.Net.Http;
910
using System.Reflection;
1011
using System.Reflection.Emit;
1112
using Microsoft.Azure.WebJobs.Script.Binding;
@@ -92,12 +93,19 @@ protected override Collection<ParameterDescriptor> GetFunctionParameters(IFuncti
9293
ParameterInfo[] parameters = functionTarget.GetParameters();
9394
Collection<ParameterDescriptor> descriptors = new Collection<ParameterDescriptor>();
9495
IEnumerable<FunctionBinding> bindings = inputBindings.Union(outputBindings);
96+
bool addHttpRequestSystemParameter = false;
9597
foreach (var parameter in parameters)
9698
{
9799
// Is it the trigger parameter?
98100
if (string.Compare(parameter.Name, triggerMetadata.Name, StringComparison.Ordinal) == 0)
99101
{
100102
descriptors.Add(CreateTriggerParameterDescriptor(parameter, triggerMetadata));
103+
104+
if (triggerMetadata.Type == BindingType.HttpTrigger &&
105+
parameter.ParameterType != typeof(HttpRequestMessage))
106+
{
107+
addHttpRequestSystemParameter = true;
108+
}
101109
}
102110
else
103111
{
@@ -133,6 +141,13 @@ protected override Collection<ParameterDescriptor> GetFunctionParameters(IFuncti
133141
// Add any additional common System parameters
134142
// Add ExecutionContext to provide access to InvocationId, etc.
135143
descriptors.Add(new ParameterDescriptor("context", typeof(ExecutionContext)));
144+
145+
// If we have an HTTP trigger binding but we're not binding
146+
// to the HttpRequestMessage, require it as a system parameter
147+
if (addHttpRequestSystemParameter)
148+
{
149+
descriptors.Add(new ParameterDescriptor(ScriptConstants.DefaultSystemTriggerParameterName, typeof(HttpRequestMessage)));
150+
}
136151

137152
return descriptors;
138153
}

0 commit comments

Comments
 (0)