Skip to content

Commit

Permalink
Fix #273
Browse files Browse the repository at this point in the history
Script had special logic to allow this for CSX; but VS tooling and direct SDK users didn't have the same logic. Add the httprequest binding logic in the binding.
  • Loading branch information
MikeStall committed Sep 7, 2017
1 parent 9472c45 commit d058f29
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public void Initialize(ExtensionConfigContext context)
}

context.Config.RegisterBindingExtension(new HttpTriggerAttributeBindingProvider(this.SetResponse));
context.Config.RegisterBindingExtension(new HttpDirectRequestBindingProvider());
}
}
}
Binary file modified src/WebJobs.Extensions.Http/GlobalSuppressions.cs
Binary file not shown.
96 changes: 96 additions & 0 deletions src/WebJobs.Extensions.Http/HttpDirectRequestBindingProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Protocols;
using static Microsoft.Azure.WebJobs.Extensions.Http.HttpTriggerAttributeBindingProvider.HttpTriggerBinding;

namespace Microsoft.Azure.WebJobs.Extensions.Http
{
// support allowing binding any non-attributed HttpRequestMessage parameter to the incoming http request.
// Foo([HttpTrigger] Poco x, HttpRequestMessage y);
internal class HttpDirectRequestBindingProvider : IBindingProvider
{
public Task<IBinding> TryCreateAsync(BindingProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}

ParameterInfo parameter = context.Parameter;
if (parameter.ParameterType == typeof(HttpRequestMessage))
{
// Not already claimed by another trigger?
if (!HasBindingAttributes(parameter))
{
return Task.FromResult<IBinding>(new HttpRequestBinding());
}
}
return Task.FromResult<IBinding>(null);
}

private static bool HasBindingAttributes(ParameterInfo parameter)
{
foreach (Attribute attr in parameter.GetCustomAttributes(false))
{
if (IsBindingAttribute(attr))
{
return true;
}
}
return false;
}

private static bool IsBindingAttribute(Attribute attribute)
{
return attribute.GetType().GetCustomAttribute<BindingAttribute>() != null;
}

public class HttpRequestBinding : IBinding
{
public bool FromAttribute
{
get
{
return false;
}
}

public Task<IValueProvider> BindAsync(BindingContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var request = context.BindingData[HttpTriggerAttributeBindingProvider.RequestBindingName];

return BindAsync(request, context.ValueContext);
}

public Task<IValueProvider> BindAsync(object value, ValueBindingContext context)
{
var request = value as HttpRequestMessage;
if (request != null)
{
var binding = new SimpleValueProvider(typeof(HttpRequestMessage), request, "request");
return Task.FromResult<IValueProvider>(binding);
}
throw new InvalidOperationException("value must be a HttpRequestMessage");
}

public ParameterDescriptor ToParameterDescriptor()
{
return new ParameterDescriptor
{
Name = "request"
};
}
}
}
}
12 changes: 10 additions & 2 deletions src/WebJobs.Extensions.Http/HttpTriggerAttributeBindingProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ internal class HttpTriggerAttributeBindingProvider : ITriggerBindingProvider
internal const string HttpQueryKey = "Query";
internal const string HttpHeadersKey = "Headers";

// Name of binding data slot where we place the full HttpRequestMessage
internal const string RequestBindingName = "$request";

// Name of binding data slot where return value is placed.
internal const string ReturnBindingName = "$return";

private readonly Action<HttpRequestMessage, object> _responseHook;

public HttpTriggerAttributeBindingProvider(Action<HttpRequestMessage, object> responseHook)
Expand Down Expand Up @@ -140,6 +146,8 @@ public async Task<ITriggerData> BindAsync(object value, ValueBindingContext cont
var aggregateBindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
aggregateBindingData.AddRange(userTypeBindingData);

aggregateBindingData[RequestBindingName] = request;

// Apply additional binding data coming from request route, query params, etc.
var requestBindingData = await GetRequestBindingDataAsync(request, _bindingDataContract);
aggregateBindingData.AddRange(requestBindingData);
Expand Down Expand Up @@ -210,7 +218,7 @@ internal Dictionary<string, Type> GetBindingDataContract(HttpTriggerAttribute at
}

// Mark that HttpTrigger accepts a return value.
aggregateDataContract["$return"] = typeof(object).MakeByRefType();
aggregateDataContract[ReturnBindingName] = typeof(object).MakeByRefType();

// add contract members for any route parameters
if (!string.IsNullOrEmpty(attribute.Route))
Expand Down Expand Up @@ -441,7 +449,7 @@ public string ToInvokeString()
}
}

private class SimpleValueProvider : IValueProvider
internal class SimpleValueProvider : IValueProvider
{
private readonly Type _type;
private readonly object _value;
Expand Down
1 change: 1 addition & 0 deletions src/WebJobs.Extensions.Http/WebJobs.Extensions.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
<Compile Include="Config\HttpExtensionConfiguration.cs" />
<Compile Include="Config\HttpJobHostConfigurationExtensions.cs" />
<Compile Include="Extensions\HttpRequestMessageExtensions.cs" />
<Compile Include="HttpDirectRequestBindingProvider.cs" />
<Compile Include="HttpExtensionConstants.cs" />
<Compile Include="Extensions\IDictionaryExtensions.cs" />
<Compile Include="GlobalSuppressions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public async Task BindAsync_Poco_FromRequestBody()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(4, triggerData.BindingData.Count);
Assert.Equal(5, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);

Expand All @@ -160,7 +160,7 @@ public async Task BindAsync_Poco_WebHookData()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(4, triggerData.BindingData.Count);
Assert.Equal(5, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);

Expand All @@ -180,7 +180,7 @@ public async Task BindAsync_Poco_FromQueryParameters()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(4, triggerData.BindingData.Count);
Assert.Equal(5, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);

Expand Down Expand Up @@ -208,7 +208,7 @@ public async Task BindAsync_Poco_FromRouteParameters()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(4, triggerData.BindingData.Count);
Assert.Equal(5, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);

Expand Down Expand Up @@ -243,7 +243,7 @@ public async Task BindAsync_Poco_MergedBindingData()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(8, triggerData.BindingData.Count);
Assert.Equal(9, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);
Assert.Equal("(425) 555-6666", triggerData.BindingData["Phone"]);
Expand Down Expand Up @@ -276,7 +276,7 @@ public async Task BindAsync_HttpRequestMessage_FromRequestBody()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(4, triggerData.BindingData.Count);
Assert.Equal(5, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);

Expand All @@ -296,7 +296,7 @@ public async Task BindAsync_HttpRequestMessage_FromQueryParameters()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(4, triggerData.BindingData.Count);
Assert.Equal(5, triggerData.BindingData.Count);
Assert.Equal("Mathew Charles", triggerData.BindingData["Name"]);
Assert.Equal("Seattle", triggerData.BindingData["Location"]);

Expand All @@ -318,7 +318,7 @@ public async Task BindAsync_String()
ValueBindingContext context = new ValueBindingContext(functionContext, CancellationToken.None);
ITriggerData triggerData = await binding.BindAsync(request, context);

Assert.Equal(2, triggerData.BindingData.Count);
Assert.Equal(3, triggerData.BindingData.Count);

string result = (string)(await triggerData.ValueProvider.GetValueAsync());
Assert.Equal("This is a test", result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Xunit;
using Newtonsoft.Json;
using System.Text;

namespace Microsoft.Azure.WebJobs.Extensions.Tests.Extensions.Http
{
Expand Down Expand Up @@ -64,6 +66,44 @@ public void BasicResponse()
Assert.Equal(request.Properties["$ret"], "test-response"); // Verify resposne was set
}

// Verify we can do a host.call and pass in a httprequestmessage directly apart from the [HttpTrigger].
[Fact]
public void BindToRequestAndPocoSeparately()
{
var json = JsonConvert.SerializeObject(new TestFunctions.MyPoco { Value = "abc" });
var request = new HttpRequestMessage(HttpMethod.Post, "http://functions.com/api/abc")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
var directRequest = new HttpRequestMessage();

// Trigger data is specificaly the parameter with [HttpTrigger], not the parameter of type request.
var method = typeof(TestFunctions).GetMethod("TestMultiBinding");
_host.Call(method, new {
triggerValue = request,
directRequest = directRequest // overides the implicit HttpRequestMessage binding.
});

Assert.Equal(directRequest.Properties["result"], "abc"); // Verify resposne was set
}

// verify that we can bind [HttpTrigger] to a poco and still get a HttpRequestMessage.
[Fact]
public void BindToRequestAndPocoAtSameTime()
{
var json = JsonConvert.SerializeObject(new TestFunctions.MyPoco { Value = "abc" });
var request = new HttpRequestMessage(HttpMethod.Post, "http://functions.com/api/abc")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};

// Trigger data is specificaly the parameter with [HttpTrigger], not the parameter of type request.
var method = typeof(TestFunctions).GetMethod("TestMultiBinding");
_host.Call(method, new { triggerValue = request, });

Assert.Equal(request.Properties["result"], "abc"); // Verify resposne was set
}

[Fact]
public async Task QueryHeaderBindingParameters()
{
Expand Down Expand Up @@ -140,6 +180,19 @@ public static Task<string> TestResponse(
// Return value becomes the HttpResponseMessage.
return Task.FromResult("test-response");
}

// Test that we can bind to both a Poco and the direct request message
public static void TestMultiBinding(
[HttpTrigger("get", "post")] MyPoco triggerValue,
HttpRequestMessage directRequest)
{
directRequest.Properties["result"] = triggerValue.Value;
}

public class MyPoco
{
public string Value { get; set; }
}
}
}
}

0 comments on commit d058f29

Please sign in to comment.