Skip to content

Commit 59ea8ea

Browse files
author
Matt Mason
committed
Added AppSetting, removed AllowTokens
1 parent fb1ed41 commit 59ea8ea

File tree

7 files changed

+254
-177
lines changed

7 files changed

+254
-177
lines changed

CustomDictionary.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@
7474
<Word>ScopeId</Word>
7575
<Word>poco</Word>
7676
<Word>ResolutionPolicyType</Word>
77-
</Recognized>
77+
<Word>AppSetting</Word>
78+
<Word>AutoResolve</Word>
79+
</Recognized>
7880
<Deprecated/>
7981
<Compound>
8082
<Term CompoundAlternate="CancellationToken">cancellationToken</Term>

src/Microsoft.Azure.WebJobs.Host/Bindings/AttributeCloner.cs

+108-155
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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
7+
{
8+
/// <summary>
9+
/// Place this on binding attributes properties to tell the binders that that the property
10+
/// should be automatically resolved as an app setting
11+
/// </summary>
12+
[Obsolete("Not ready for public consumption.")]
13+
[AttributeUsage(AttributeTargets.Property)]
14+
public sealed class AppSettingAttribute : Attribute
15+
{
16+
/// <summary>
17+
/// Creates a new instance.
18+
/// </summary>
19+
public AppSettingAttribute()
20+
{
21+
}
22+
23+
/// <summary>
24+
/// The default app setting name to use, if none specified
25+
/// </summary>
26+
public string Default { get; set; }
27+
}
28+
}

src/Microsoft.Azure.WebJobs/AutoResolveAttribute.cs

-8
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,8 @@ public sealed class AutoResolveAttribute : Attribute
1818
/// </summary>
1919
public AutoResolveAttribute()
2020
{
21-
AllowTokens = true;
2221
}
2322

24-
/// <summary>
25-
/// If true, the property allows tokens. Any value within %% will be automatically resolved.
26-
/// If false, the property does not allow tokens. The property value itself is automatically resolved.
27-
/// Default value is true.
28-
/// </summary>
29-
public bool AllowTokens { get; set; }
30-
3123
/// <summary>
3224
/// Specifies a type to use for runtime binding resolution. That type must derive from IResolutionPolicy, found
3325
/// in the Microsoft.Azure.WebJobs.Host assembly.

src/Microsoft.Azure.WebJobs/WebJobs.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<Compile Include="..\Common\CommonAssemblyInfo.cs">
5959
<Link>Properties\CommonAssemblyInfo.cs</Link>
6060
</Compile>
61+
<Compile Include="AppSettingAttribute.cs" />
6162
<Compile Include="AutoResolveAttribute.cs" />
6263
<Compile Include="BlobTriggerAttribute.cs" />
6364
<Compile Include="BinderExtensions.cs" />

test/Microsoft.Azure.WebJobs.Host.UnitTests/AttributeClonerTests.cs

+113-13
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,37 @@ public Attr2(string resolvedProp2, string constantProp)
4040

4141
public string ConstantProp { get; private set; }
4242

43-
[AutoResolve(AllowTokens = false)]
43+
[AppSetting]
4444
public string ResolvedSetting { get; set; }
45+
46+
[AppSetting(Default = "default")]
47+
public string DefaultSetting { get; set; }
48+
}
49+
50+
public class Attr3: Attribute
51+
{
52+
[AppSetting]
53+
public string Required { get; set; }
54+
55+
[AppSetting(Default = "default")]
56+
public string Default { get; set; }
57+
}
58+
59+
public class Attr4: Attribute
60+
{
61+
[AppSetting]
62+
public string AppSetting { get; set; }
63+
64+
[AutoResolve]
65+
public string AutoResolve { get; set; }
66+
}
67+
68+
public class InvalidAnnotation: Attribute
69+
{
70+
// only one of appsetting/autoresolve allowed
71+
[AppSetting]
72+
[AutoResolve]
73+
public string Required { get; set; }
4574
}
4675

4776
public class AttributeWithResolutionPolicy : Attribute
@@ -159,7 +188,9 @@ public async Task InvokeStringBlobAttribute()
159188
[Fact]
160189
public async Task InvokeStringMultipleResolvedProperties()
161190
{
162-
Attr2 attr = new Attr2("{p2}", "constant") { ResolvedProp1 = "{p1}" };
191+
Attr2 attr = new Attr2("{p2}", "constant") {
192+
ResolvedProp1 = "{p1}"
193+
};
163194

164195
var cloner = new AttributeCloner<Attr2>(attr, GetBindingContract("p1", "p2"));
165196

@@ -232,12 +263,52 @@ public void Setting()
232263
}
233264

234265
[Fact]
235-
public void Setting_WithNoValueInResolver_Throws()
266+
public void Setting_WithNoValueInResolver_ThrowsIfNoDefault()
236267
{
237268
Attr2 a2 = new Attr2(string.Empty, string.Empty) { ResolvedSetting = "appsetting" };
238269
Assert.Throws<InvalidOperationException>(() => new AttributeCloner<Attr2>(a2, EmptyContract, null));
239270
}
240271

272+
[Fact]
273+
public void Setting_WithNoValueInResolver_UsesDefault()
274+
{
275+
Attr2 a2 = new Attr2(string.Empty, string.Empty) { ResolvedSetting = "appsetting" };
276+
var nameResolver = new FakeNameResolver().Add("appsetting", "ABC").Add("default", "default");
277+
var cloner = new AttributeCloner<Attr2>(a2, EmptyContract, nameResolver);
278+
279+
var a2Cloned = cloner.GetNameResolvedAttribute();
280+
Assert.Equal("default", a2Cloned.DefaultSetting);
281+
}
282+
283+
[Fact]
284+
public void AppSettingAttribute_Resolves_IfDefaultSet()
285+
{
286+
Attr3 a3 = new Attr3() { Required = "req", Default = "env" };
287+
var nameResolver = new FakeNameResolver().Add("env", "envval").Add("req", "reqval");
288+
var cloner = new AttributeCloner<Attr3>(a3, EmptyContract, nameResolver);
289+
var cloned = cloner.GetNameResolvedAttribute();
290+
Assert.Equal("reqval", cloned.Required);
291+
Assert.Equal("envval", cloned.Default);
292+
}
293+
294+
[Fact]
295+
public void AppSettingAttribute_Resolves_IfDefaultMatched()
296+
{
297+
Attr3 a3 = new Attr3() { Required = "req" };
298+
var nameResolver = new FakeNameResolver().Add("default", "defaultval").Add("req", "reqval");
299+
var cloner = new AttributeCloner<Attr3>(a3, EmptyContract, nameResolver);
300+
var cloned = cloner.GetNameResolvedAttribute();
301+
Assert.Equal("reqval", cloned.Required);
302+
Assert.Equal("defaultval", cloned.Default);
303+
}
304+
305+
[Fact]
306+
public void AppSettingAttribute_Throws_IfDefaultUnmatched()
307+
{
308+
Attr3 a3 = new Attr3() { Required = "req" };
309+
Assert.Throws<InvalidOperationException>(() => new AttributeCloner<Attr3>(a3, EmptyContract, null));
310+
}
311+
241312
[Fact]
242313
public async Task Setting_Null()
243314
{
@@ -250,6 +321,32 @@ public async Task Setting_Null()
250321
Assert.Null(a2Clone.ResolvedSetting);
251322
}
252323

324+
[Fact]
325+
public void AppSettingAttribute_ResolvesWholeValueAsSetting()
326+
{
327+
Attr4 a4 = new Attr4();
328+
var name = "test{x}and%y%";
329+
a4.AppSetting = a4.AutoResolve = name;
330+
331+
var nameResolver = new FakeNameResolver()
332+
.Add("y", "Setting")
333+
.Add(name, "AppSetting");
334+
var cloner = new AttributeCloner<Attr4>(a4, GetBindingContract("x"), nameResolver);
335+
var cloned = cloner.GetNameResolvedAttribute();
336+
// autoresolve resolves tokens
337+
Assert.Equal("test{x}andSetting", cloned.AutoResolve);
338+
// appsetting treats entire string as app setting name
339+
Assert.Equal("AppSetting", cloned.AppSetting);
340+
}
341+
342+
[Fact]
343+
public void AttributeCloner_Throws_IfAppSettingAndAutoResolve()
344+
{
345+
InvalidAnnotation a = new InvalidAnnotation();
346+
var exc = Assert.Throws<InvalidOperationException>(() => new AttributeCloner<InvalidAnnotation>(a, EmptyContract, null));
347+
Assert.Equal("Property 'Required' cannot be annotated with both AppSetting and AutoResolve.", exc.Message);
348+
}
349+
253350
[Fact]
254351
public async Task CloneNoDefaultCtor()
255352
{
@@ -326,18 +423,19 @@ public void TryAutoResolveValue_UnresolvedValue_ThrowsExpectedException()
326423
ResolvedSetting = "MySetting"
327424
};
328425
var prop = attribute.GetType().GetProperty("ResolvedSetting");
329-
string resolvedValue = null;
426+
var attr = prop.GetCustomAttribute<AppSettingAttribute>();
427+
string resolvedValue = "MySetting";
330428

331-
var ex = Assert.Throws<InvalidOperationException>(() => AttributeCloner<Attr2>.TryAutoResolveValue(attribute, prop, resolver, out resolvedValue));
429+
var ex = Assert.Throws<InvalidOperationException>(() => AttributeCloner<Attr2>.GetAppSettingResolver(resolvedValue, attr, resolver, prop));
332430
Assert.Equal("Unable to resolve value for property 'Attr2.ResolvedSetting'.", ex.Message);
333431
}
334432

335433
[Fact]
336434
public void GetPolicy_ReturnsDefault_WhenNoSpecifiedPolicy()
337435
{
338436
PropertyInfo propInfo = typeof(AttributeWithResolutionPolicy).GetProperty(nameof(AttributeWithResolutionPolicy.PropWithoutPolicy));
339-
340-
IResolutionPolicy policy = AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(propInfo);
437+
AutoResolveAttribute attr = propInfo.GetCustomAttribute<AutoResolveAttribute>();
438+
IResolutionPolicy policy = AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(attr.ResolutionPolicyType, propInfo);
341439

342440
Assert.IsType<DefaultResolutionPolicy>(policy);
343441
}
@@ -347,7 +445,8 @@ public void GetPolicy_Returns_SpecifiedPolicy()
347445
{
348446
PropertyInfo propInfo = typeof(AttributeWithResolutionPolicy).GetProperty(nameof(AttributeWithResolutionPolicy.PropWithPolicy));
349447

350-
IResolutionPolicy policy = AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(propInfo);
448+
AutoResolveAttribute attr = propInfo.GetCustomAttribute<AutoResolveAttribute>();
449+
IResolutionPolicy policy = AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(attr.ResolutionPolicyType, propInfo);
351450

352451
Assert.IsType<TestResolutionPolicy>(policy);
353452
}
@@ -359,7 +458,8 @@ public void GetPolicy_ReturnsODataFilterPolicy_ForMarkerType()
359458
// because BindingTemplate doesn't exist in the core assembly.
360459
PropertyInfo propInfo = typeof(AttributeWithResolutionPolicy).GetProperty(nameof(AttributeWithResolutionPolicy.PropWithMarkerPolicy));
361460

362-
IResolutionPolicy policy = AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(propInfo);
461+
AutoResolveAttribute attr = propInfo.GetCustomAttribute<AutoResolveAttribute>();
462+
IResolutionPolicy policy = AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(attr.ResolutionPolicyType, propInfo);
363463

364464
Assert.IsType<Host.Bindings.ODataFilterResolutionPolicy>(policy);
365465
}
@@ -368,8 +468,8 @@ public void GetPolicy_ReturnsODataFilterPolicy_ForMarkerType()
368468
public void GetPolicy_Throws_IfPolicyDoesNotImplementInterface()
369469
{
370470
PropertyInfo propInfo = typeof(AttributeWithResolutionPolicy).GetProperty(nameof(AttributeWithResolutionPolicy.PropWithInvalidPolicy));
371-
372-
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(propInfo));
471+
AutoResolveAttribute attr = propInfo.GetCustomAttribute<AutoResolveAttribute>();
472+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(attr.ResolutionPolicyType, propInfo));
373473

374474
Assert.Equal($"The {nameof(AutoResolveAttribute.ResolutionPolicyType)} on {nameof(AttributeWithResolutionPolicy.PropWithInvalidPolicy)} must derive from {typeof(IResolutionPolicy).Name}.", ex.Message);
375475
}
@@ -378,8 +478,8 @@ public void GetPolicy_Throws_IfPolicyDoesNotImplementInterface()
378478
public void GetPolicy_Throws_IfPolicyHasNoDefaultConstructor()
379479
{
380480
PropertyInfo propInfo = typeof(AttributeWithResolutionPolicy).GetProperty(nameof(AttributeWithResolutionPolicy.PropWithConstructorlessPolicy));
381-
382-
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(propInfo));
481+
AutoResolveAttribute attr = propInfo.GetCustomAttribute<AutoResolveAttribute>();
482+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => AttributeCloner<AttributeWithResolutionPolicy>.GetPolicy(attr.ResolutionPolicyType, propInfo));
383483

384484
Assert.Equal($"The {nameof(AutoResolveAttribute.ResolutionPolicyType)} on {nameof(AttributeWithResolutionPolicy.PropWithConstructorlessPolicy)} must derive from {typeof(IResolutionPolicy).Name} and have a default constructor.", ex.Message);
385485
}

test/Microsoft.Azure.WebJobs.Host.UnitTests/PublicSurfaceTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ public void WebJobsPublicSurface_LimitedToSpecificTypes()
108108
{
109109
"IAttributeInvokeDescriptor`1",
110110
"AutoResolveAttribute",
111+
"AppSettingAttribute",
111112
"BinderExtensions",
112113
"BlobAttribute",
113114
"BlobTriggerAttribute",

0 commit comments

Comments
 (0)