diff --git a/CustomDictionary.xml b/CustomDictionary.xml
index 5fada7ba9..7674556d3 100644
--- a/CustomDictionary.xml
+++ b/CustomDictionary.xml
@@ -73,6 +73,7 @@
Prefetch
ScopeId
poco
+ ResolutionPolicyType
diff --git a/README.md b/README.md
index f31265069..0f1fcd892 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
Azure WebJobs SDK
===
+
+|Branch|Status|
+|---|---|
+|dev|[![Build status](https://ci.appveyor.com/api/projects/status/3qmk6ukn942q220j/branch/dev?svg=true)](https://ci.appveyor.com/project/appsvc/azure-webjobs-sdk-rqm4t/branch/dev)|
+|master|[![Build status](https://ci.appveyor.com/api/projects/status/3qmk6ukn942q220j/branch/master?svg=true)](https://ci.appveyor.com/project/appsvc/azure-webjobs-sdk-rqm4t/branch/master)|
+
The **Azure WebJobs SDK** is a framework that simplifies the task of writing background processing code that runs in Azure. The Azure WebJobs SDK includes a declarative **binding** and **trigger** system that works with Azure Storage Blobs, Queues and Tables as well as Service Bus. The binding system makes it incredibly easy to write code that reads or writes Azure Storage objects. The trigger system automatically invokes a function in your code whenever any new data is received in a queue or blob.
In addition to the built in triggers/bindings, the WebJobs SDK is **fully extensible**, allowing new types of triggers/bindings to be created and plugged into the framework in a first class way. See [Azure WebJobs SDK Extensions](https://github.com/Azure/azure-webjobs-sdk-extensions) for details. Many useful extensions have already been created and can be used in your applications today. Extensions include a File trigger/binder, a Timer/Cron trigger, a WebHook HTTP trigger, as well as a SendGrid email binding.
diff --git a/WebJobs.proj b/WebJobs.proj
index 63c380cb4..0fab8adc4 100644
--- a/WebJobs.proj
+++ b/WebJobs.proj
@@ -70,7 +70,7 @@
-
+
@@ -141,6 +141,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -233,7 +248,7 @@
{
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
- webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFileName);
+ webClient.DownloadFile("https://dist.nuget.org/win-x86-commandline/latest/nuget.exe", OutputFileName);
}
return true;
diff --git a/WebJobs.sln b/WebJobs.sln
index e79d93a72..88b9f1f6c 100644
--- a/WebJobs.sln
+++ b/WebJobs.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{639967B0-0544-4C52-94AC-9A3D25E33256}"
EndProject
@@ -52,6 +52,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebJobs.Logging", "src\Micr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebJobs.Logging.FunctionalTests", "test\Microsoft.Azure.WebJobs.Logging.FunctionalTests\WebJobs.Logging.FunctionalTests.csproj", "{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sample", "Sample", "{72A798F0-699B-4C8E-8D43-C1749661471E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleHost", "sample\SampleHost\SampleHost.csproj", "{93429246-CCE9-4EB0-B94D-68522862BA79}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -122,6 +126,10 @@ Global
{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {93429246-CCE9-4EB0-B94D-68522862BA79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {93429246-CCE9-4EB0-B94D-68522862BA79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {93429246-CCE9-4EB0-B94D-68522862BA79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {93429246-CCE9-4EB0-B94D-68522862BA79}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -135,5 +143,6 @@ Global
{28BC5EE0-9227-4124-AA8F-1C0CAA218D12} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{C6B834AB-7B6A-47AE-A7C3-C102B0C861FF} = {639967B0-0544-4C52-94AC-9A3D25E33256}
{C8EAAE01-E8CF-4131-9D4B-F0FDF00DA4BE} = {639967B0-0544-4C52-94AC-9A3D25E33256}
+ {93429246-CCE9-4EB0-B94D-68522862BA79} = {72A798F0-699B-4C8E-8D43-C1749661471E}
EndGlobalSection
EndGlobal
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 000000000..b09520183
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,9 @@
+build_script:
+ - msbuild WebJobs.proj /t:BuildTestBinaries /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /p:OutputPath=%APPVEYOR_BUILD_FOLDER%\bin
+
+test_script:
+ - vstest.console /logger:Appveyor /TestAdapterPath:bin bin/Microsoft.Azure.WebJobs.Host.UnitTests.dll bin/Microsoft.Azure.WebJobs.Host.FunctionalTests.dll bin/Microsoft.Azure.WebJobs.ServiceBus.UnitTests.dll bin/Dashboard.UnitTests.dll bin/Microsoft.Azure.WebJobs.Host.EndToEndTests.dll
+
+# if you need to rdp into build machine to investigate
+# on_finish:
+# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
diff --git a/perf/Microsoft.Azure.WebJobs.Perf/WebJobs.Perf.csproj b/perf/Microsoft.Azure.WebJobs.Perf/WebJobs.Perf.csproj
index 5796a39cb..cbe1724d7 100644
--- a/perf/Microsoft.Azure.WebJobs.Perf/WebJobs.Perf.csproj
+++ b/perf/Microsoft.Azure.WebJobs.Perf/WebJobs.Perf.csproj
@@ -24,7 +24,7 @@
prompt
4
true
- 5
+ default
pdbonly
@@ -34,7 +34,7 @@
prompt
4
true
- 5
+ default
@@ -45,15 +45,15 @@
True
- ..\..\packages\Microsoft.Data.Edm.5.8.1\lib\net40\Microsoft.Data.Edm.dll
+ ..\..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll
True
- ..\..\packages\Microsoft.Data.OData.5.8.1\lib\net40\Microsoft.Data.OData.dll
+ ..\..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll
True
- ..\..\packages\Microsoft.Data.Services.Client.5.8.1\lib\net40\Microsoft.Data.Services.Client.dll
+ ..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
@@ -72,7 +72,7 @@
- ..\..\packages\System.Spatial.5.8.1\lib\net40\System.Spatial.dll
+ ..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
True
diff --git a/perf/Microsoft.Azure.WebJobs.Perf/app.config b/perf/Microsoft.Azure.WebJobs.Perf/app.config
index 53d004e55..8e91b3a8c 100644
--- a/perf/Microsoft.Azure.WebJobs.Perf/app.config
+++ b/perf/Microsoft.Azure.WebJobs.Perf/app.config
@@ -17,7 +17,7 @@
-
+
\ No newline at end of file
diff --git a/perf/Microsoft.Azure.WebJobs.Perf/packages.config b/perf/Microsoft.Azure.WebJobs.Perf/packages.config
index dbcdd7985..da5913211 100644
--- a/perf/Microsoft.Azure.WebJobs.Perf/packages.config
+++ b/perf/Microsoft.Azure.WebJobs.Perf/packages.config
@@ -1,12 +1,16 @@
-
-
-
+
+
+
-
+
+
+
+
+
diff --git a/sample/SampleHost/App.config b/sample/SampleHost/App.config
new file mode 100644
index 000000000..f634f3861
--- /dev/null
+++ b/sample/SampleHost/App.config
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/SampleHost/Functions.cs b/sample/SampleHost/Functions.cs
new file mode 100644
index 000000000..e90876ca7
--- /dev/null
+++ b/sample/SampleHost/Functions.cs
@@ -0,0 +1,33 @@
+// 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 Microsoft.Azure.WebJobs;
+using Newtonsoft.Json.Linq;
+
+namespace SampleHost
+{
+ public static class Functions
+ {
+ public static void BlobTrigger(
+ [BlobTrigger("test")] string blob)
+ {
+ Console.WriteLine("Processed blob: " + blob);
+ }
+
+ public static void BlobPoisonBlobHandler(
+ [QueueTrigger("webjobs-blobtrigger-poison")] JObject blobInfo)
+ {
+ string container = (string)blobInfo["ContainerName"];
+ string blobName = (string)blobInfo["BlobName"];
+
+ Console.WriteLine($"Poison blob: {container}/{blobName}");
+ }
+
+ public static void QueueTrigger(
+ [QueueTrigger("test")] string message)
+ {
+ Console.WriteLine("Processed message: " + message);
+ }
+ }
+}
diff --git a/sample/SampleHost/Program.cs b/sample/SampleHost/Program.cs
new file mode 100644
index 000000000..248613df9
--- /dev/null
+++ b/sample/SampleHost/Program.cs
@@ -0,0 +1,26 @@
+// 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 Microsoft.Azure.WebJobs;
+
+namespace SampleHost
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var config = new JobHostConfiguration();
+ config.Queues.VisibilityTimeout = TimeSpan.FromSeconds(15);
+ config.Queues.MaxDequeueCount = 3;
+
+ if (config.IsDevelopment)
+ {
+ config.UseDevelopmentSettings();
+ }
+
+ var host = new JobHost(config);
+ host.RunAndBlock();
+ }
+ }
+}
diff --git a/sample/SampleHost/Properties/AssemblyInfo.cs b/sample/SampleHost/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..9de00bcd5
--- /dev/null
+++ b/sample/SampleHost/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SampleHost")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SampleHost")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("93429246-cce9-4eb0-b94d-68522862ba79")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/sample/SampleHost/SampleHost.csproj b/sample/SampleHost/SampleHost.csproj
new file mode 100644
index 000000000..3a36addfd
--- /dev/null
+++ b/sample/SampleHost/SampleHost.csproj
@@ -0,0 +1,76 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {93429246-CCE9-4EB0-B94D-68522862BA79}
+ Exe
+ Properties
+ SampleHost
+ SampleHost
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0e095cb2-3030-49ff-966c-785f1a55f0c1}
+ WebJobs.Host
+
+
+ {e3f2b2c8-6b8d-4d6a-a3ae-98366c9f3b49}
+ WebJobs
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/SampleHost/packages.config b/sample/SampleHost/packages.config
new file mode 100644
index 000000000..9d64bf364
--- /dev/null
+++ b/sample/SampleHost/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Dashboard/ApiControllers/FunctionsController.cs b/src/Dashboard/ApiControllers/FunctionsController.cs
index 8550d0b9e..c86b9050c 100644
--- a/src/Dashboard/ApiControllers/FunctionsController.cs
+++ b/src/Dashboard/ApiControllers/FunctionsController.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Reflection;
using System.Threading.Tasks;
using System.Web;
using System.Web.Caching;
@@ -249,7 +250,7 @@ public async Task GetRecentInvocationsTimeline(
{
StartBucket = entity.TimeBucket,
Start = entity.Time,
- TotalPass = entity.TotalPass,
+ TotalPass = entity.TotalPass,
TotalFail = entity.TotalFail,
TotalRun = entity.TotalRun
});
@@ -341,6 +342,7 @@ private InvocationLogViewModel CreateInvocationEntry(RecentInvocationEntry entry
metadataSnapshot.EndTime = entry.EndTime;
metadataSnapshot.Succeeded = entry.Succeeded;
metadataSnapshot.Heartbeat = entry.Heartbeat;
+ metadataSnapshot.FunctionInstanceHeartbeatExpiry = entry.FunctionInstanceHeartbeatExpiry;
return new InvocationLogViewModel(metadataSnapshot, HostInstanceHasHeartbeat(metadataSnapshot));
}
@@ -465,7 +467,10 @@ private static ParameterModel[] CreateParameterModels(CloudStorageAccount accoun
// If host is specified, then only return definitions for that host. If null, return all hosts.
[Route("api/functions/definitions")]
- public IHttpActionResult GetFunctionDefinitions([FromUri]PagingInfo pagingInfo, string host = null)
+ public IHttpActionResult GetFunctionDefinitions(
+ [FromUri]PagingInfo pagingInfo,
+ string host = null,
+ bool skipStats = false)
{
if (pagingInfo == null)
{
@@ -518,7 +523,9 @@ public IHttpActionResult GetFunctionDefinitions([FromUri]PagingInfo pagingInfo,
model.IsOldHost = OnlyBeta1HostExists(alreadyFoundNoNewerEntries: true);
}
- if (model.Entries != null)
+ // This is very slow. Allow a flag to skip it, and then client can query the stats independently
+ // via the /timeline API.
+ if ((model.Entries != null) && !skipStats)
{
foreach (FunctionStatisticsViewModel statisticsModel in model.Entries)
{
@@ -579,6 +586,19 @@ public IHttpActionResult Abort(string instanceQueueName)
return Ok();
}
+ // Diagnostics endpoint, getting the version of the service that's running.
+ [Route("api/version")]
+ public IHttpActionResult GetVersionInfo()
+ {
+ var assembly = this.GetType().Assembly;
+ AssemblyFileVersionAttribute fileVersionAttr = assembly.GetCustomAttribute();
+
+ return Ok(new
+ {
+ Version = fileVersionAttr.Version
+ });
+ }
+
private bool? HostHasHeartbeat(FunctionIndexEntry function)
{
if (!function.HeartbeatExpirationInSeconds.HasValue)
@@ -626,6 +646,12 @@ public IHttpActionResult Abort(string instanceQueueName)
private bool? HostInstanceHasHeartbeat(FunctionInstanceSnapshot snapshot)
{
+ if (snapshot.FunctionInstanceHeartbeatExpiry.HasValue)
+ {
+ var now = DateTime.UtcNow;
+ return snapshot.FunctionInstanceHeartbeatExpiry.Value > now;
+ }
+
HeartbeatDescriptor heartbeat = snapshot.Heartbeat;
if (heartbeat == null)
diff --git a/src/Dashboard/Dashboard.csproj b/src/Dashboard/Dashboard.csproj
index 31f51a1bc..bf17ab9a3 100644
--- a/src/Dashboard/Dashboard.csproj
+++ b/src/Dashboard/Dashboard.csproj
@@ -37,7 +37,7 @@
prompt
4
false
- 5
+ default
pdbonly
@@ -47,7 +47,7 @@
prompt
4
false
- 5
+ default
@@ -68,15 +68,15 @@
- ..\..\packages\Microsoft.Data.Edm.5.8.1\lib\net40\Microsoft.Data.Edm.dll
+ ..\..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll
True
- ..\..\packages\Microsoft.Data.OData.5.8.1\lib\net40\Microsoft.Data.OData.dll
+ ..\..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll
True
- ..\..\packages\Microsoft.Data.Services.Client.5.8.1\lib\net40\Microsoft.Data.Services.Client.dll
+ ..\..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
@@ -100,7 +100,7 @@
True
- ..\..\packages\System.Spatial.5.8.1\lib\net40\System.Spatial.dll
+ ..\..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
True
@@ -513,9 +513,9 @@
This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
+
-
+
TMessage" converter, then that takes precedence.
- // Else, bind over an array of "byte --> TMessage" converters
-
- argumentBuilder = DynamicInvokeBuildOutArgument(elementType, converterManager, buildFromAttribute, cloner);
-
- if (argumentBuilder != null)
- {
- }
- else if (elementType.IsArray)
- {
- if (elementType == typeof(TMessage[]))
- {
- argumentBuilder = (attrResolved, context) =>
- {
- IAsyncCollector raw = buildFromAttribute(attrResolved);
- var invokeString = cloner.GetInvokeString(attrResolved);
- return new OutArrayValueProvider(raw, invokeString);
- };
- }
- else
- {
- // out TMessage[]
- var e2 = elementType.GetElementType();
- argumentBuilder = DynamicBuildOutArrayArgument(e2, converterManager, buildFromAttribute, cloner);
- }
- }
- else
- {
- // Single enqueue
- // out TMessage
- if (elementType == typeof(TMessage))
- {
- argumentBuilder = (attrResolved, context) =>
- {
- IAsyncCollector raw = buildFromAttribute(attrResolved);
- var invokeString = cloner.GetInvokeString(attrResolved);
- return new OutValueProvider(raw, invokeString);
- };
- }
- }
-
- // For out-param, give some rich errors.
- if (argumentBuilder == null)
- {
- if (typeof(IEnumerable).IsAssignableFrom(elementType))
- {
- throw new InvalidOperationException(
- "Enumerable types are not supported. Use ICollector or IAsyncCollector instead.");
- }
- else if (typeof(object) == elementType)
- {
- throw new InvalidOperationException("Object element types are not supported.");
- }
- }
-
- return argumentBuilder;
- }
-
- private static FuncArgumentBuilder DynamicBuildOutArrayArgument(
- Type typeMessageSrc,
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- var method = typeof(BindingFactoryHelpers).GetMethod("BuildOutArrayArgument", BindingFlags.NonPublic | BindingFlags.Static);
- method = method.MakeGenericMethod(typeof(TAttribute), typeof(TMessage), typeMessageSrc);
- var argumentBuilder = MethodInvoke>(method, cm, buildFromAttribute, cloner);
- return argumentBuilder;
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Dynamically invoked")]
- private static FuncArgumentBuilder BuildOutArrayArgument(
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- // Other
- var convert = cm.GetConverter();
- FuncArgumentBuilder argumentBuilder = (attrResolved, context) =>
- {
- IAsyncCollector raw = buildFromAttribute(attrResolved);
- IAsyncCollector obj = new TypedAsyncCollectorAdapter(
- raw, convert, attrResolved, context);
- string invokeString = cloner.GetInvokeString(attrResolved);
- return new OutArrayValueProvider(obj, invokeString);
- };
- return argumentBuilder;
- }
-
- // Helper to dynamically invoke BuildICollectorArgument with the proper generics
- // Can we bind to 'out TUser'? Requires converter manager to supply a TUser--> TMessage converter.
- // Return null if we can't bind it.
- private static FuncArgumentBuilder DynamicInvokeBuildOutArgument(
- Type typeMessageSrc,
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- var method = typeof(BindingFactoryHelpers).GetMethod("BuildOutArgument", BindingFlags.NonPublic | BindingFlags.Static);
- method = method.MakeGenericMethod(typeof(TAttribute), typeof(TMessage), typeMessageSrc);
- var argumentBuilder = MethodInvoke>(method, cm, buildFromAttribute, cloner);
- return argumentBuilder;
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Dynamically invoked")]
- private static FuncArgumentBuilder BuildOutArgument(
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- // Other
- var convert = cm.GetConverter();
- if (convert == null)
- {
- return null;
- }
- FuncArgumentBuilder argumentBuilder = (attrResolved, context) =>
- {
- IAsyncCollector raw = buildFromAttribute(attrResolved);
- IAsyncCollector obj = new TypedAsyncCollectorAdapter(
- raw, convert, attrResolved, context);
- string invokeString = cloner.GetInvokeString(attrResolved);
- return new OutValueProvider(obj, invokeString);
- };
- return argumentBuilder;
- }
-
- // Helper to dynamically invoke BuildICollectorArgument with the proper generics
- private static FuncArgumentBuilder DynamicInvokeBuildICollectorArgument(
- Type typeMessageSrc,
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- var method = typeof(BindingFactoryHelpers).GetMethod("BuildICollectorArgument", BindingFlags.NonPublic | BindingFlags.Static);
- method = method.MakeGenericMethod(typeof(TAttribute), typeof(TMessage), typeMessageSrc);
- var argumentBuilder = MethodInvoke>(method, cm, buildFromAttribute, cloner);
- return argumentBuilder;
- }
-
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Dynamic invoke")]
- private static FuncArgumentBuilder BuildICollectorArgument(
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- // Other
- var convert = cm.GetConverter();
- if (convert == null)
- {
- ThrowMissingConversionError(typeof(TMessageSrc));
- }
- FuncArgumentBuilder argumentBuilder = (attrResolved, context) =>
- {
- IAsyncCollector raw = buildFromAttribute(attrResolved);
- IAsyncCollector obj = new TypedAsyncCollectorAdapter(
- raw, convert, attrResolved, context);
- ICollector obj2 = new SyncAsyncCollectorAdapter(obj);
- string invokeString = cloner.GetInvokeString(attrResolved);
- return new AsyncCollectorValueProvider, TMessage>(obj2, raw, invokeString);
- };
- return argumentBuilder;
- }
-
- // Helper to dynamically invoke BuildIAsyncCollectorArgument with the proper generics
- private static FuncArgumentBuilder DynamicInvokeBuildIAsyncCollectorArgument(
- Type typeMessageSrc,
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- var method = typeof(BindingFactoryHelpers).GetMethod("BuildIAsyncCollectorArgument", BindingFlags.NonPublic | BindingFlags.Static);
- method = method.MakeGenericMethod(typeof(TAttribute), typeof(TMessage), typeMessageSrc);
- var argumentBuilder = MethodInvoke>(method, cm, buildFromAttribute, cloner);
- return argumentBuilder;
- }
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Dynamically invoked")]
- private static FuncArgumentBuilder BuildIAsyncCollectorArgument(
- IConverterManager cm,
- Func> buildFromAttribute,
- AttributeCloner cloner)
- where TAttribute : Attribute
- {
- var convert = cm.GetConverter();
- if (convert == null)
- {
- ThrowMissingConversionError(typeof(TMessageSrc));
- }
- FuncArgumentBuilder argumentBuilder = (attrResolved, context) =>
- {
- IAsyncCollector raw = buildFromAttribute(attrResolved);
- IAsyncCollector obj = new TypedAsyncCollectorAdapter(
- raw, convert, attrResolved, context);
- var invokeString = cloner.GetInvokeString(attrResolved);
- return new AsyncCollectorValueProvider, TMessage>(obj, raw, invokeString);
- };
- return argumentBuilder;
- }
-
- // typeUser - type in the user's parameter.
- private static void ThrowMissingConversionError(Type typeUser)
- {
- if (typeUser.IsPrimitive)
- {
- throw new NotSupportedException("Primitive types are not supported.");
- }
-
- if (typeof(IEnumerable).IsAssignableFrom(typeUser))
- {
- throw new InvalidOperationException("Nested collections are not supported.");
- }
- throw new InvalidOperationException("Can't convert from type '" + typeUser.FullName);
- }
-
- // Helper to invoke and unwrap teh target exception.
- private static TReturn MethodInvoke(MethodInfo method, params object[] args)
+
+ // Helper to invoke and unwrap the target exception.
+ public static TReturn MethodInvoke(MethodInfo method, params object[] args)
{
try
{
diff --git a/src/Microsoft.Azure.WebJobs.Host/Bindings/BindingProviders/AsyncCollectorBindingProvider.cs b/src/Microsoft.Azure.WebJobs.Host/Bindings/BindingProviders/AsyncCollectorBindingProvider.cs
new file mode 100644
index 000000000..bfa94b6f4
--- /dev/null
+++ b/src/Microsoft.Azure.WebJobs.Host/Bindings/BindingProviders/AsyncCollectorBindingProvider.cs
@@ -0,0 +1,299 @@
+// 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.Collections;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.Azure.WebJobs.Host.Protocols;
+
+namespace Microsoft.Azure.WebJobs.Host.Bindings
+{
+ // General rule for binding parameters to an AsyncCollector.
+ // Supports the various flavors like IAsyncCollector, ICollector, out T, out T[].
+ internal class AsyncCollectorBindingProvider : FluentBindingProvider, IBindingProvider
+ where TAttribute : Attribute
+ {
+ private readonly INameResolver _nameResolver;
+ private readonly IConverterManager _converterManager;
+ private readonly PatternMatcher _patternMatcher;
+
+ public AsyncCollectorBindingProvider(
+ INameResolver nameResolver,
+ IConverterManager converterManager,
+ PatternMatcher patternMatcher)
+ {
+ this._nameResolver = nameResolver;
+ this._converterManager = converterManager;
+ this._patternMatcher = patternMatcher;
+ }
+
+ // Describe different flavors of IAsyncCollector bindings.
+ private enum Mode
+ {
+ IAsyncCollector,
+ ICollector,
+ OutSingle,
+ OutArray
+ }
+
+ public Task TryCreateAsync(BindingProviderContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ var parameter = context.Parameter;
+
+ var mode = GetMode(parameter);
+ if (mode == null)
+ {
+ return Task.FromResult(null);
+ }
+
+ var type = typeof(ExactBinding<>).MakeGenericType(typeof(TAttribute), typeof(TType), mode.ElementType);
+ var method = type.GetMethod("TryBuild", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ var binding = BindingFactoryHelpers.MethodInvoke(method, this, mode.Mode, context);
+
+ return Task.FromResult(binding);
+ }
+
+ // Parse the signature to determine which mode this is.
+ // Can also check with converter manager to disambiguate some cases.
+ private CollectorBindingPattern GetMode(ParameterInfo parameter)
+ {
+ Type parameterType = parameter.ParameterType;
+ if (parameterType.IsGenericType)
+ {
+ var genericType = parameterType.GetGenericTypeDefinition();
+ var elementType = parameterType.GetGenericArguments()[0];
+
+ if (genericType == typeof(IAsyncCollector<>))
+ {
+ return new CollectorBindingPattern(Mode.IAsyncCollector, elementType);
+ }
+ else if (genericType == typeof(ICollector<>))
+ {
+ return new CollectorBindingPattern(Mode.ICollector, elementType);
+ }
+
+ // A different interface. Let another rule try it.
+ return null;
+ }
+
+ if (parameter.IsOut)
+ {
+ // How should "out byte[]" bind?
+ // If there's an explicit "byte[] --> TMessage" converter, then that takes precedence.
+ // Else, bind over an array of "byte --> TMessage" converters
+ Type elementType = parameter.ParameterType.GetElementType();
+ bool hasConverter = this._converterManager.HasConverter(elementType, typeof(TType));
+ if (hasConverter)
+ {
+ // out T, where T might be an array
+ return new CollectorBindingPattern(Mode.OutSingle, elementType);
+ }
+
+ if (elementType.IsArray)
+ {
+ // out T[]
+ var messageType = elementType.GetElementType();
+ return new CollectorBindingPattern(Mode.OutArray, messageType);
+ }
+
+ var validator = ConverterManager.GetTypeValidator();
+ if (validator.IsMatch(elementType))
+ {
+ // out T, t is not an array
+ return new CollectorBindingPattern(Mode.OutSingle, elementType);
+ }
+
+ // For out-param ,we don't expect another rule to claim it. So give some rich errors on mismatch.
+ if (typeof(IEnumerable).IsAssignableFrom(elementType))
+ {
+ throw new InvalidOperationException(
+ "Enumerable types are not supported. Use ICollector or IAsyncCollector instead.");
+ }
+ else if (typeof(object) == elementType)
+ {
+ throw new InvalidOperationException("Object element types are not supported.");
+ }
+ }
+
+ // No match. Let another rule claim it
+ return null;
+ }
+
+ // Represent the different possible flavors for binding to an async collector
+ private class CollectorBindingPattern
+ {
+ public CollectorBindingPattern(Mode mode, Type elementType)
+ {
+ this.Mode = mode;
+ this.ElementType = elementType;
+ }
+ public Mode Mode { get; set; }
+ public Type ElementType { get; set; }
+ }
+
+ // TType - specified in the rule.
+ // TMessage - element type of the IAsyncCollector<> we matched to.
+ private class ExactBinding : BindingBase
+ {
+ private readonly Func