Skip to content

Commit

Permalink
Bot-support.
Browse files Browse the repository at this point in the history
Add a builtin 'sys' object, which includes a MethodName  property that allows extensions to now get the calling method name. Bot uses this.
Allows binding expressions like {sys.MethodName }

Fix #1110
Fix TryGetAssembly to infer types from BindToInput<T> for bot extension.

This supports https://github.com/Microsoft/BotBuilder-WebJobs-BotExtension/pull/6
  • Loading branch information
MikeStall committed May 18, 2017
1 parent f7b5e1b commit 962dfa6
Show file tree
Hide file tree
Showing 20 changed files with 409 additions and 89 deletions.
106 changes: 69 additions & 37 deletions src/Microsoft.Azure.WebJobs.Host/Bindings/AttributeCloner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Host.Bindings.Path;

namespace Microsoft.Azure.WebJobs.Host.Bindings
{
{
using BindingData = IReadOnlyDictionary<string, object>;
using BindingDataContract = IReadOnlyDictionary<string, System.Type>;
// Func to transform Attribute,BindingData into value for cloned attribute property/constructor arg
Expand All @@ -34,20 +35,15 @@ internal class AttributeCloner<TAttribute>
// Compute the values to apply to Settable properties on newly created attribute.
private readonly Action<TAttribute, BindingData>[] _propertySetters;

// Optional hook for post-processing the attribute. This is intended for legacy hack rules.
private readonly Func<TAttribute, Task<TAttribute>> _hook;

private readonly Dictionary<PropertyInfo, AutoResolveAttribute> _autoResolves = new Dictionary<PropertyInfo, AutoResolveAttribute>();

private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;

public AttributeCloner(
internal AttributeCloner(
TAttribute source,
BindingDataContract bindingDataContract,
INameResolver nameResolver = null,
Func<TAttribute, Task<TAttribute>> hook = null)
INameResolver nameResolver = null)
{
_hook = hook;
nameResolver = nameResolver ?? new EmptyNameResolver();
_source = source;

Expand Down Expand Up @@ -95,38 +91,58 @@ private BindingDataResolver GetResolver(PropertyInfo propInfo, INameResolver nam
object originalValue = propInfo.GetValue(_source);
AppSettingAttribute appSettingAttr = propInfo.GetCustomAttribute<AppSettingAttribute>();
AutoResolveAttribute autoResolveAttr = propInfo.GetCustomAttribute<AutoResolveAttribute>();


if (appSettingAttr == null && autoResolveAttr == null)
{
// No special attributes, treat as literal.
return (newAttr, bindingData) => originalValue;
}

if (appSettingAttr != null && autoResolveAttr != null)
{
throw new InvalidOperationException($"Property '{propInfo.Name}' cannot be annotated with both AppSetting and AutoResolve.");
}

// attributes only work on string properties.
if (propInfo.PropertyType != typeof(string))
{
throw new InvalidOperationException($"AutoResolve or AppSetting property '{propInfo.Name}' must be of type string.");
}
var str = (string)originalValue;

// first try to resolve with app setting
if (appSettingAttr != null)
{
return GetAppSettingResolver((string)originalValue, appSettingAttr, nameResolver, propInfo);
return GetAppSettingResolver(str, appSettingAttr, nameResolver, propInfo);
}

// Must have an [AutoResolve]
// try to resolve with auto resolve ({...}, %...%)
if (autoResolveAttr != null && originalValue != null)
return GetAutoResolveResolver(str, autoResolveAttr, nameResolver, propInfo, contract);
}

// Apply AutoResolve attribute
internal BindingDataResolver GetAutoResolveResolver(string originalValue, AutoResolveAttribute autoResolveAttr, INameResolver nameResolver, PropertyInfo propInfo, BindingDataContract contract)
{
if (string.IsNullOrWhiteSpace(originalValue))
{
if (autoResolveAttr.Default != null)
{
return GetBuiltinTemplateResolver(autoResolveAttr.Default, nameResolver);
}
else
{
return (newAttr, bindingData) => originalValue;
}
}
else
{
_autoResolves[propInfo] = autoResolveAttr;
return GetTemplateResolver((string)originalValue, autoResolveAttr, nameResolver, propInfo, contract);
return GetTemplateResolver(originalValue, autoResolveAttr, nameResolver, propInfo, contract);
}
// resolve the original value
return (newAttr, bindingData) => originalValue;
}

// AutoResolve
internal static BindingDataResolver GetTemplateResolver(string originalValue, AutoResolveAttribute attr, INameResolver nameResolver, PropertyInfo propInfo, BindingDataContract contract)
{
string resolvedValue = nameResolver.ResolveWholeString(originalValue);
var template = BindingTemplate.FromString(resolvedValue);
IResolutionPolicy policy = GetPolicy(attr.ResolutionPolicyType, propInfo);
template.ValidateContractCompatibility(contract);
return (newAttr, bindingData) => TemplateBind(policy, propInfo, newAttr, template, bindingData);
}

// AppSetting
// Apply AppSetting attribute.
internal static BindingDataResolver GetAppSettingResolver(string originalValue, AppSettingAttribute attr, INameResolver nameResolver, PropertyInfo propInfo)
{
string appSettingName = originalValue ?? attr.Default;
Expand All @@ -145,6 +161,30 @@ internal static BindingDataResolver GetAppSettingResolver(string originalValue,
return (newAttr, bindingData) => resolvedValue;
}

// Resolve for AutoResolve.Default templates.
// These only have access to the {sys} builtin variable and don't get access to trigger binding data.
internal static BindingDataResolver GetBuiltinTemplateResolver(string originalValue, INameResolver nameResolver)
{
string resolvedValue = nameResolver.ResolveWholeString(originalValue);

var template = BindingTemplate.FromString(resolvedValue);

SystemBindingData.ValidateStaticContract(template);

// For static default contracts, we only have access to the built in binding data.
return (newAttr, bindingData) => template.Bind(SystemBindingData.GetSystemBindingData(bindingData));
}

// AutoResolve
internal static BindingDataResolver GetTemplateResolver(string originalValue, AutoResolveAttribute attr, INameResolver nameResolver, PropertyInfo propInfo, BindingDataContract contract)
{
string resolvedValue = nameResolver.ResolveWholeString(originalValue);
var template = BindingTemplate.FromString(resolvedValue);
IResolutionPolicy policy = GetPolicy(attr.ResolutionPolicyType, propInfo);
template.ValidateContractCompatibility(contract);
return (newAttr, bindingData) => TemplateBind(policy, propInfo, newAttr, template, bindingData);
}

// Get a attribute with %% resolved, but not runtime {} resolved.
public TAttribute GetNameResolvedAttribute()
{
Expand All @@ -169,7 +209,7 @@ public string GetInvokeString(TAttribute attributeResolved)
return invokeString;
}

public async Task<TAttribute> ResolveFromInvokeStringAsync(string invokeString)
public TAttribute ResolveFromInvokeString(string invokeString)
{
TAttribute attr;
var resolver = _source as IAttributeInvokeDescriptor<TAttribute>;
Expand All @@ -181,20 +221,12 @@ public async Task<TAttribute> ResolveFromInvokeStringAsync(string invokeString)
{
attr = resolver.FromInvokeString(invokeString);
}
if (_hook != null)
{
attr = await _hook(attr);
}
return attr;
}

public async Task<TAttribute> ResolveFromBindingDataAsync(BindingContext ctx)
public TAttribute ResolveFromBindingData(BindingContext ctx)
{
var attr = ResolveFromBindings(ctx.BindingData);
if (_hook != null)
{
attr = await _hook(attr);
}
return attr;
}

Expand Down Expand Up @@ -242,7 +274,7 @@ internal TAttribute New(IDictionary<string, string> overrideProperties)
return newAttr;
}

internal TAttribute ResolveFromBindings(IReadOnlyDictionary<string, object> bindingData)
internal TAttribute ResolveFromBindings(BindingData bindingData)
{
// Invoke ctor
var ctorArgs = Array.ConvertAll(_ctorParamResolvers, func => func(_source, bindingData));
Expand All @@ -256,7 +288,7 @@ internal TAttribute ResolveFromBindings(IReadOnlyDictionary<string, object> bind
return newAttr;
}

private static string TemplateBind(IResolutionPolicy policy, PropertyInfo prop, Attribute attr, BindingTemplate template, IReadOnlyDictionary<string, object> bindingData)
private static string TemplateBind(IResolutionPolicy policy, PropertyInfo prop, Attribute attr, BindingTemplate template, BindingData bindingData)
{
if (bindingData == null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Azure.WebJobs.Host/Bindings/BindingBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public bool FromAttribute

public async Task<IValueProvider> BindAsync(BindingContext context)
{
var attrResolved = await Cloner.ResolveFromBindingDataAsync(context);
var attrResolved = Cloner.ResolveFromBindingData(context);
return await BuildAsync(attrResolved, context.ValueContext);
}

Expand All @@ -57,7 +57,7 @@ public async Task<IValueProvider> BindAsync(object value, ValueBindingContext co
{
// Called when we invoke from dashboard.
// str --> attribute --> obj
var resolvedAttr = await Cloner.ResolveFromInvokeStringAsync(str);
var resolvedAttr = Cloner.ResolveFromInvokeString(str);
return await BuildAsync(resolvedAttr, context);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,7 @@ public static ExactBinding<TMessage> TryBuild(

var parameter = context.Parameter;
var attributeSource = TypeUtility.GetResolvedAttribute<TAttribute>(parameter);

Func<TAttribute, Task<TAttribute>> hookWrapper = null;
if (parent.PostResolveHook != null)
{
hookWrapper = (attrResolved) => parent.PostResolveHook(attrResolved, parameter, parent._nameResolver);
}


Func<object, object> buildFromAttribute;
FuncConverter<TMessage, TAttribute, TType> converter = null;

Expand Down Expand Up @@ -316,7 +310,7 @@ public static ExactBinding<TMessage> TryBuild(
};
}

var cloner = new AttributeCloner<TAttribute>(attributeSource, context.BindingDataContract, parent._nameResolver, hookWrapper);
var cloner = new AttributeCloner<TAttribute>(attributeSource, context.BindingDataContract, parent._nameResolver);
return new ExactBinding<TMessage>(cloner, param, mode, buildFromAttribute, converter);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,7 @@ public static ExactBinding<TUserType> TryBuild(
var parameter = context.Parameter;
var attributeSource = TypeUtility.GetResolvedAttribute<TAttribute>(parameter);

Func<TAttribute, Task<TAttribute>> hookWrapper = null;
if (parent.PostResolveHook != null)
{
hookWrapper = (attrResolved) => parent.PostResolveHook(attrResolved, parameter, parent._nameResolver);
}

var cloner = new AttributeCloner<TAttribute>(attributeSource, context.BindingDataContract, parent._nameResolver, hookWrapper);
var cloner = new AttributeCloner<TAttribute>(attributeSource, context.BindingDataContract, parent._nameResolver);

Func<object, object> buildFromAttribute;
FuncConverter<TType, TAttribute, TUserType> converter = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ namespace Microsoft.Azure.WebJobs.Host
internal class FluentBindingProvider<TAttribute>
{
protected internal Func<TAttribute, ParameterInfo, INameResolver, ParameterDescriptor> BuildParameterDescriptor { get; set; }
protected internal Func<TAttribute, ParameterInfo, INameResolver, Task<TAttribute>> PostResolveHook { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ namespace Microsoft.Azure.WebJobs.Host
{
// Internal Extension methods for setting backwards compatibility hooks on certain bindings.
// This keeps the hooks out of the public surface.
internal static class FluidBindingProviderExtensions
internal static class FluentBindingProviderExtensions
{
public static IBindingProvider SetPostResolveHook<TAttribute>(
this IBindingProvider binder,
Func<TAttribute, ParameterInfo, INameResolver, ParameterDescriptor> buildParameterDescriptor = null,
Func<TAttribute, ParameterInfo, INameResolver, Task<TAttribute>> postResolveHook = null)
Func<TAttribute, ParameterInfo, INameResolver, ParameterDescriptor> buildParameterDescriptor = null)
{
var fluidBinder = (FluentBindingProvider<TAttribute>)binder;

fluidBinder.PostResolveHook = postResolveHook;

fluidBinder.BuildParameterDescriptor = buildParameterDescriptor;
return binder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ private static void ValidateContractCompatibility(IEnumerable<string> parameterN
{
foreach (string parameterName in parameterNames)
{
if (string.Equals(parameterName, SystemBindingData.Name, StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (BindingParameterResolver.IsSystemParameter(parameterName))
{
continue;
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/Bindings/ConverterManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,9 @@ public ExactMatch(Type type)
{
_type = type;
}

internal Type ExactType => _type;

public override bool IsMatch(Type type)
{
return type == _type;
Expand Down
13 changes: 12 additions & 1 deletion src/Microsoft.Azure.WebJobs.Host/Bindings/FunctionBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ internal static BindingContext NewBindingContext(
// if bindingData was a mutable dictionary, we could just add it.
// But since it's read-only, must create a new one.
Dictionary<string, object> bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

var funcContext = context.FunctionContext;
var methodName = funcContext.MethodName;

if (existingBindingData != null)
{
foreach (var kv in existingBindingData)
Expand All @@ -45,7 +49,14 @@ internal static BindingContext NewBindingContext(
bindingData[kv.Key] = kv.Value;
}
}


// Add 'sys' binding data.
var sysBindingData = new SystemBindingData
{
MethodName = methodName
};
sysBindingData.AddToBindingData(bindingData);

BindingContext bindingContext = new BindingContext(context, bindingData);
return bindingContext;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Threading;
using Microsoft.Azure.WebJobs.Host.Protocols;

namespace Microsoft.Azure.WebJobs.Host.Bindings
{
Expand All @@ -22,11 +23,18 @@ public class FunctionBindingContext
/// <param name="functionInstanceId">The instance ID of the function being bound to.</param>
/// <param name="functionCancellationToken">The <see cref="CancellationToken"/> to use.</param>
/// <param name="trace">The trace writer.</param>
public FunctionBindingContext(Guid functionInstanceId, CancellationToken functionCancellationToken, TraceWriter trace)
/// <param name="functionDescriptor">Current function being executed. </param>
public FunctionBindingContext(
Guid functionInstanceId,
CancellationToken functionCancellationToken,
TraceWriter trace,
FunctionDescriptor functionDescriptor = null)
{
_functionInstanceId = functionInstanceId;
_functionCancellationToken = functionCancellationToken;
_trace = trace;

this.MethodName = functionDescriptor?.Method?.Name;
}

/// <summary>
Expand All @@ -52,5 +60,10 @@ public TraceWriter Trace
{
get { return _trace; }
}

/// <summary>
/// The short name of the current function.
/// </summary>
public string MethodName { get; private set; }
}
}
Loading

0 comments on commit 962dfa6

Please sign in to comment.