Skip to content

Commit 494918a

Browse files
committed
Adding Table binding support
1 parent 3e0bb26 commit 494918a

File tree

11 files changed

+193
-17
lines changed

11 files changed

+193
-17
lines changed

sample/QueueTrigger-Powershell/function.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
"type": "queueTrigger",
66
"queueName": "samples-powershell"
77
}
8+
],
9+
"output": [
10+
{
11+
"type": "table",
12+
"name": "output",
13+
"tableName": "samples",
14+
"partitionKey": "samples",
15+
"rowKey": "%rand-guid%"
16+
}
817
]
918
}
1019
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
$in = [Console]::ReadLine()
22

3-
[Console]::WriteLine("Powershell script processed queue message '$in'")
3+
[Console]::WriteLine("Powershell script processed queue message '$in'")
4+
5+
$output = $Env:output
6+
$json = $in | ConvertFrom-Json
7+
$entity = [string]::Format('{{ "timestamp": "{0}", "title": "Powershell Table Entity for message {1}" }}', $(get-date -f MM-dd-yyyy_HH_mm_ss), $json.id)
8+
$entity | Out-File -Encoding Ascii $output

src/WebJobs.Script/Binding/Binding.cs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
using System.Collections.ObjectModel;
66
using System.IO;
77
using System.Threading.Tasks;
8+
using Microsoft.Azure.WebJobs.Host;
89
using Newtonsoft.Json.Linq;
910

1011
namespace Microsoft.Azure.WebJobs.Script
1112
{
1213
internal abstract class Binding
1314
{
14-
public Binding(string name, string type, FileAccess fileAccess, bool isTrigger)
15+
private readonly JobHostConfiguration _config;
16+
17+
protected Binding(JobHostConfiguration config, string name, string type, FileAccess fileAccess, bool isTrigger)
1518
{
19+
_config = config;
1620
Name = name;
1721
Type = type;
1822
FileAccess = fileAccess;
@@ -31,7 +35,7 @@ public Binding(string name, string type, FileAccess fileAccess, bool isTrigger)
3135

3236
public abstract Task BindAsync(IBinder binder, Stream stream, IReadOnlyDictionary<string, string> bindingData);
3337

34-
internal static Collection<Binding> GetBindings(JArray bindingArray, FileAccess fileAccess)
38+
internal static Collection<Binding> GetBindings(JobHostConfiguration config, JArray bindingArray, FileAccess fileAccess)
3539
{
3640
Collection<Binding> bindings = new Collection<Binding>();
3741

@@ -45,27 +49,44 @@ internal static Collection<Binding> GetBindings(JArray bindingArray, FileAccess
4549
if (type == "blob")
4650
{
4751
string path = (string)binding["path"];
48-
bindings.Add(new BlobBinding(name, path, fileAccess, isTrigger: false));
52+
bindings.Add(new BlobBinding(config, name, path, fileAccess, isTrigger: false));
4953
}
5054
else if (type == "blobTrigger")
5155
{
5256
string path = (string)binding["path"];
53-
bindings.Add(new BlobBinding(name, path, fileAccess, isTrigger: true));
57+
bindings.Add(new BlobBinding(config, name, path, fileAccess, isTrigger: true));
5458
}
5559
else if (type == "queue")
5660
{
5761
string queueName = (string)binding["queueName"];
58-
bindings.Add(new QueueBinding(name, queueName, fileAccess, isTrigger: false));
62+
bindings.Add(new QueueBinding(config, name, queueName, fileAccess, isTrigger: false));
5963
}
6064
else if (type == "queueTrigger")
6165
{
6266
string queueName = (string)binding["queueName"];
63-
bindings.Add(new QueueBinding(name, queueName, fileAccess, isTrigger: true));
67+
bindings.Add(new QueueBinding(config, name, queueName, fileAccess, isTrigger: true));
68+
}
69+
else if (type == "table")
70+
{
71+
string tableName = (string)binding["tableName"];
72+
string partitionKey = (string)binding["partitionKey"];
73+
string rowKey = (string)binding["rowKey"];
74+
bindings.Add(new TableBinding(config, name, tableName, partitionKey, rowKey, fileAccess));
6475
}
6576
}
6677
}
6778

6879
return bindings;
6980
}
81+
82+
protected string Resolve(string name)
83+
{
84+
if (_config.NameResolver == null)
85+
{
86+
return name;
87+
}
88+
89+
return _config.NameResolver.ResolveWholeString(name);
90+
}
7091
}
7192
}

src/WebJobs.Script/Binding/BlobBinding.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal class BlobBinding : Binding
1313
{
1414
private readonly BindingTemplate _pathBindingTemplate;
1515

16-
public BlobBinding(string name, string path, FileAccess fileAccess, bool isTrigger) : base(name, "blob", fileAccess, isTrigger)
16+
public BlobBinding(JobHostConfiguration config, string name, string path, FileAccess fileAccess, bool isTrigger) : base(config, name, "blob", fileAccess, isTrigger)
1717
{
1818
Path = path;
1919
_pathBindingTemplate = BindingTemplate.FromString(Path);
@@ -37,6 +37,8 @@ public override async Task BindAsync(IBinder binder, Stream stream, IReadOnlyDic
3737
boundBlobPath = _pathBindingTemplate.Bind(bindingData);
3838
}
3939

40+
boundBlobPath = Resolve(boundBlobPath);
41+
4042
Stream blobStream = binder.Bind<Stream>(new BlobAttribute(boundBlobPath, FileAccess));
4143
if (FileAccess == FileAccess.Write)
4244
{

src/WebJobs.Script/Binding/QueueBinding.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal class QueueBinding : Binding
1313
{
1414
private readonly BindingTemplate _queueNameBindingTemplate;
1515

16-
public QueueBinding(string name, string queueName, FileAccess fileAccess, bool isTrigger) : base(name, "queue", fileAccess, isTrigger)
16+
public QueueBinding(JobHostConfiguration config, string name, string queueName, FileAccess fileAccess, bool isTrigger) : base(config, name, "queue", fileAccess, isTrigger)
1717
{
1818
QueueName = queueName;
1919
_queueNameBindingTemplate = BindingTemplate.FromString(QueueName);
@@ -37,6 +37,8 @@ public override async Task BindAsync(IBinder binder, Stream stream, IReadOnlyDic
3737
boundQueueName = _queueNameBindingTemplate.Bind(bindingData);
3838
}
3939

40+
boundQueueName = Resolve(boundQueueName);
41+
4042
if (FileAccess == FileAccess.Write)
4143
{
4244
Stream queueStream = binder.Bind<Stream>(new QueueAttribute(boundQueueName));
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Threading.Tasks;
8+
using Microsoft.Azure.WebJobs.Host.Bindings.Path;
9+
using Microsoft.WindowsAzure.Storage;
10+
using Microsoft.WindowsAzure.Storage.Table;
11+
using Newtonsoft.Json.Linq;
12+
13+
namespace Microsoft.Azure.WebJobs.Script
14+
{
15+
internal class TableBinding : Binding
16+
{
17+
private readonly BindingTemplate _partitionKeyBindingTemplate;
18+
private readonly BindingTemplate _rowKeyBindingTemplate;
19+
20+
public TableBinding(JobHostConfiguration config, string name, string tableName, string partitionKey, string rowKey, FileAccess fileAccess) : base(config, name, "queue", fileAccess, false)
21+
{
22+
TableName = tableName;
23+
PartitionKey = partitionKey;
24+
RowKey = rowKey;
25+
_partitionKeyBindingTemplate = BindingTemplate.FromString(PartitionKey);
26+
_rowKeyBindingTemplate = BindingTemplate.FromString(RowKey);
27+
}
28+
29+
public string TableName { get; private set; }
30+
public string PartitionKey { get; private set; }
31+
public string RowKey { get; private set; }
32+
33+
public override bool HasBindingParameters
34+
{
35+
get
36+
{
37+
return _partitionKeyBindingTemplate.ParameterNames.Any() ||
38+
_rowKeyBindingTemplate.ParameterNames.Any();
39+
}
40+
}
41+
42+
public override async Task BindAsync(IBinder binder, Stream stream, IReadOnlyDictionary<string, string> bindingData)
43+
{
44+
string boundPartitionKey = PartitionKey;
45+
string boundRowKey = RowKey;
46+
if (bindingData != null)
47+
{
48+
boundPartitionKey = _partitionKeyBindingTemplate.Bind(bindingData);
49+
boundRowKey = _rowKeyBindingTemplate.Bind(bindingData);
50+
}
51+
52+
boundPartitionKey = Resolve(boundPartitionKey);
53+
boundRowKey = Resolve(boundRowKey);
54+
55+
if (FileAccess == FileAccess.Write)
56+
{
57+
// read the content as a JObject
58+
JObject jsonObject = null;
59+
using (StreamReader streamReader = new StreamReader(stream))
60+
{
61+
string content = await streamReader.ReadToEndAsync();
62+
jsonObject = JObject.Parse(content);
63+
}
64+
65+
// TODO: If RowKey has not been specified in the binding, try to
66+
// derive from the object properties (e.g. "rowKey" or "id" properties);
67+
68+
IAsyncCollector<DynamicTableEntity> collector = binder.Bind<IAsyncCollector<DynamicTableEntity>>(new TableAttribute(TableName));
69+
DynamicTableEntity tableEntity = new DynamicTableEntity(boundPartitionKey, boundRowKey);
70+
foreach (JProperty property in jsonObject.Properties())
71+
{
72+
EntityProperty entityProperty = EntityProperty.CreateEntityPropertyFromObject((object)property.Value);
73+
tableEntity.Properties.Add(property.Name, entityProperty);
74+
}
75+
76+
await collector.AddAsync(tableEntity);
77+
}
78+
else
79+
{
80+
DynamicTableEntity tableEntity = binder.Bind<DynamicTableEntity>(new TableAttribute(TableName, boundPartitionKey, boundRowKey));
81+
if (tableEntity != null)
82+
{
83+
OperationContext context = new OperationContext();
84+
var entityProperties = tableEntity.WriteEntity(context);
85+
86+
JObject jsonObject = new JObject();
87+
foreach (var entityProperty in entityProperties)
88+
{
89+
jsonObject.Add(entityProperty.Key, entityProperty.Value.StringValue);
90+
}
91+
string json = jsonObject.ToString();
92+
93+
using (StreamWriter sw = new StreamWriter(stream))
94+
{
95+
await sw.WriteAsync(json);
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
6+
namespace Microsoft.Azure.WebJobs.Script
7+
{
8+
internal class NameResolver : INameResolver
9+
{
10+
private readonly Random _rand = new Random();
11+
12+
public string Resolve(string name)
13+
{
14+
switch (name.ToLowerInvariant())
15+
{
16+
case "rand-guid":
17+
return Guid.NewGuid().ToString();
18+
case "rand-int":
19+
return _rand.Next(10000, int.MaxValue).ToString();
20+
}
21+
22+
return name;
23+
}
24+
}
25+
}

src/WebJobs.Script/Config/ScriptHost.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ protected virtual void Initialize()
3232
{
3333
List<FunctionDescriptorProvider> descriptionProviders = new List<FunctionDescriptorProvider>()
3434
{
35-
new ScriptFunctionDescriptorProvider(ScriptConfig.RootPath),
36-
new NodeFunctionDescriptorProvider(ScriptConfig.RootPath)
35+
new ScriptFunctionDescriptorProvider(HostConfig, ScriptConfig.RootPath),
36+
new NodeFunctionDescriptorProvider(HostConfig, ScriptConfig.RootPath)
3737
};
3838

3939
// read host.json and apply to JobHostConfiguration
@@ -53,6 +53,7 @@ protected virtual void Initialize()
5353
types.Add(type);
5454

5555
HostConfig.TypeLocator = new TypeLocator(types);
56+
HostConfig.NameResolver = new NameResolver();
5657
}
5758

5859
public static ScriptHost Create(ScriptHostConfiguration scriptConfig = null)

src/WebJobs.Script/Description/NodeFunctionDescriptorProvider.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ namespace Microsoft.Azure.WebJobs.Script
1111
{
1212
internal class NodeFunctionDescriptorProvider : FunctionDescriptorProvider
1313
{
14+
private readonly JobHostConfiguration _config;
1415
private readonly string _rootPath;
1516

16-
public NodeFunctionDescriptorProvider(string rootPath)
17+
public NodeFunctionDescriptorProvider(JobHostConfiguration config, string rootPath)
1718
{
19+
_config = config;
1820
_rootPath = rootPath;
1921
}
2022

@@ -32,10 +34,10 @@ public override bool TryCreate(FunctionFolderInfo functionFolderInfo, out Functi
3234
// parse the bindings
3335
JObject bindings = (JObject)functionFolderInfo.Configuration["bindings"];
3436
JArray inputs = (JArray)bindings["input"];
35-
Collection<Binding> inputBindings = Binding.GetBindings(inputs, FileAccess.Read);
37+
Collection<Binding> inputBindings = Binding.GetBindings(_config, inputs, FileAccess.Read);
3638

3739
JArray outputs = (JArray)bindings["output"];
38-
Collection<Binding> outputBindings = Binding.GetBindings(outputs, FileAccess.Write);
40+
Collection<Binding> outputBindings = Binding.GetBindings(_config, outputs, FileAccess.Write);
3941

4042
JObject trigger = (JObject)inputs.FirstOrDefault(p => ((string)p["type"]).ToLowerInvariant().EndsWith("trigger"));
4143
string triggerType = (string)trigger["type"];

src/WebJobs.Script/Description/ScriptFunctionDescriptionProvider.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ namespace Microsoft.Azure.WebJobs.Script
1111
{
1212
internal class ScriptFunctionDescriptorProvider : FunctionDescriptorProvider
1313
{
14+
private readonly JobHostConfiguration _config;
1415
private readonly string _rootPath;
1516

16-
public ScriptFunctionDescriptorProvider(string rootPath)
17+
public ScriptFunctionDescriptorProvider(JobHostConfiguration config, string rootPath)
1718
{
19+
_config = config;
1820
_rootPath = rootPath;
1921
}
2022

@@ -31,10 +33,10 @@ public override bool TryCreate(FunctionFolderInfo functionFolderInfo, out Functi
3133
// parse the bindings
3234
JObject bindings = (JObject)functionFolderInfo.Configuration["bindings"];
3335
JArray inputs = (JArray)bindings["input"];
34-
Collection<Binding> inputBindings = Binding.GetBindings(inputs, FileAccess.Read);
36+
Collection<Binding> inputBindings = Binding.GetBindings(_config, inputs, FileAccess.Read);
3537

3638
JArray outputs = (JArray)bindings["output"];
37-
Collection<Binding> outputBindings = Binding.GetBindings(outputs, FileAccess.Write);
39+
Collection<Binding> outputBindings = Binding.GetBindings(_config, outputs, FileAccess.Write);
3840

3941
string scriptFilePath = Path.Combine(_rootPath, functionFolderInfo.Source);
4042
ScriptFunctionInvoker invoker = new ScriptFunctionInvoker(scriptFilePath, inputBindings, outputBindings);

0 commit comments

Comments
 (0)