Skip to content

Commit eeaf761

Browse files
committed
Load binding extensions with direct load.
Needed to allow accessing builtin extensions.
1 parent 7546f3e commit eeaf761

File tree

4 files changed

+111
-9
lines changed

4 files changed

+111
-9
lines changed

src/WebJobs.Script/GlobalSuppressions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,5 @@
211211
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Eventing.StructuredLogging.StructuredLogEntry.#ToJson()")]
212212
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Eventing.StructuredLogging.StructuredLogEntry.#ToJson()")]
213213
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_traceWriter", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Diagnostics.StructuredLogWriter.#Dispose(System.Boolean)")]
214-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.FileTraceWriter.#.ctor(System.String,System.Diagnostics.TraceLevel,System.Func`2<System.String,System.String>)")]
214+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.FileTraceWriter.#.ctor(System.String,System.Diagnostics.TraceLevel,System.Func`2<System.String,System.String>)")]
215+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptHost.#GetDirectTypes(System.Collections.Generic.IEnumerable`1<Microsoft.Azure.WebJobs.Script.Description.FunctionMetadata>)")]

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,11 @@ protected virtual void Initialize()
488488
}
489489
}
490490
LoadBuiltinBindings(usedBindingTypes);
491+
492+
var directTypes = GetDirectTypes(functionMetadata);
493+
494+
LoadDirectlyReferencesExtensions(directTypes);
495+
491496
LoadCustomExtensions();
492497

493498
// Do this after we've loaded the custom extensions. That gives an extension an opportunity to plug in their own implementations.
@@ -517,7 +522,7 @@ protected virtual void Initialize()
517522
List<Type> types = new List<Type>();
518523
types.Add(type);
519524

520-
AddDirectTypes(types, functions);
525+
types.AddRange(directTypes);
521526

522527
hostConfig.TypeLocator = new TypeLocator(types);
523528

@@ -614,13 +619,14 @@ private void VerifyPrecompileStatus(IEnumerable<FunctionDescriptor> functions)
614619
}
615620
}
616621

617-
private static void AddDirectTypes(List<Type> types, Collection<FunctionDescriptor> functions)
622+
// Get the set of types that should be directly loaded. These have the "configurationSource" : "attributes" set.
623+
// They will be indexed and invoked directly by the WebJobs SDK and skip the IL generator and invoker paths.
624+
private static IEnumerable<Type> GetDirectTypes(IEnumerable<FunctionMetadata> functionMetadatas)
618625
{
619626
HashSet<Type> visitedTypes = new HashSet<Type>();
620627

621-
foreach (var function in functions)
628+
foreach (var metadata in functionMetadatas)
622629
{
623-
var metadata = function.Metadata;
624630
if (!metadata.IsDirect)
625631
{
626632
continue;
@@ -632,11 +638,9 @@ private static void AddDirectTypes(List<Type> types, Collection<FunctionDescript
632638
Assembly assembly = Assembly.LoadFrom(path);
633639
var type = assembly.GetType(typeName);
634640

635-
if (visitedTypes.Add(type))
636-
{
637-
types.Add(type);
638-
}
641+
visitedTypes.Add(type);
639642
}
643+
return visitedTypes;
640644
}
641645

642646
private IMetricsLogger CreateMetricsLogger()
@@ -697,6 +701,19 @@ private void LoadCustomExtensions(string extensionsPath)
697701
}
698702
}
699703

704+
// Load extensions that are directly references by the user types.
705+
private void LoadDirectlyReferencesExtensions(IEnumerable<Type> userTypes)
706+
{
707+
var possibleExtensionAssemblies = UserTypeScanner.GetPossibleExtensionAssemblies(userTypes);
708+
709+
foreach (var kv in possibleExtensionAssemblies)
710+
{
711+
var assembly = kv.Key;
712+
var locationHint = kv.Value;
713+
LoadExtensions(assembly, locationHint);
714+
}
715+
}
716+
700717
private void LoadExtensions(Assembly assembly, string locationHint)
701718
{
702719
foreach (var type in assembly.ExportedTypes)
@@ -706,11 +723,31 @@ private void LoadExtensions(Assembly assembly, string locationHint)
706723
continue;
707724
}
708725

726+
if (IsExtensionLoaded(type))
727+
{
728+
continue;
729+
}
730+
709731
IExtensionConfigProvider instance = (IExtensionConfigProvider)Activator.CreateInstance(type);
710732
LoadExtension(instance, locationHint);
711733
}
712734
}
713735

736+
private bool IsExtensionLoaded(Type type)
737+
{
738+
var registry = this.ScriptConfig.HostConfig.GetService<IExtensionRegistry>();
739+
var extensions = registry.GetExtensions<IExtensionConfigProvider>();
740+
foreach (var extension in extensions)
741+
{
742+
var loadedExtentionType = extension.GetType();
743+
if (loadedExtentionType == type)
744+
{
745+
return true;
746+
}
747+
}
748+
return false;
749+
}
750+
714751
// Load a single extension
715752
private void LoadExtension(IExtensionConfigProvider instance, string locationHint = null)
716753
{
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
using System.Collections.Generic;
6+
using System.Reflection;
7+
using Microsoft.Azure.WebJobs.Description;
8+
9+
namespace Microsoft.Azure.WebJobs.Script
10+
{
11+
// Scan over user types with [Binding] attributes and extract the set of assemblies.
12+
internal class UserTypeScanner
13+
{
14+
// Scan user types for potential binding extensions.
15+
// Return mapping of new assemblies along with a hint path for what triggered each.
16+
public static Dictionary<Assembly, string> GetPossibleExtensionAssemblies(IEnumerable<Type> userTypes)
17+
{
18+
// List of possible extension assemblies.
19+
var possibleExtensionAssemblies = new Dictionary<Assembly, string>();
20+
21+
foreach (var type in userTypes)
22+
{
23+
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
24+
{
25+
ScanMethod(possibleExtensionAssemblies, method);
26+
}
27+
}
28+
return possibleExtensionAssemblies;
29+
}
30+
31+
private static void ScanMethod(Dictionary<Assembly, string> possibleExtensionAssemblies, MethodInfo method)
32+
{
33+
foreach (var parameter in method.GetParameters())
34+
{
35+
ScanParameter(possibleExtensionAssemblies, parameter);
36+
}
37+
38+
ScanParameter(possibleExtensionAssemblies, method.ReturnParameter);
39+
}
40+
41+
private static void ScanParameter(Dictionary<Assembly, string> possibleExtensionAssemblies, ParameterInfo parameter)
42+
{
43+
foreach (var attr in parameter.GetCustomAttributes())
44+
{
45+
if (IsBindingAttribute(attr))
46+
{
47+
Assembly assembly = attr.GetType().Assembly;
48+
49+
var method = parameter.Member;
50+
string hintPath = $"referenced by: Method='{method.DeclaringType.FullName}.{method.Name}', Parameter='{parameter.Name}'.";
51+
52+
possibleExtensionAssemblies[assembly] = hintPath;
53+
}
54+
}
55+
}
56+
57+
private static bool IsBindingAttribute(Attribute attribute)
58+
{
59+
var bindingAttr = attribute.GetType().GetCustomAttribute<BindingAttribute>();
60+
return bindingAttr != null;
61+
}
62+
}
63+
}

src/WebJobs.Script/WebJobs.Script.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@
520520
<Compile Include="Extensions\IMetricsLoggerExtensions.cs" />
521521
<Compile Include="Extensions\TraceWriterExtensions.cs" />
522522
<Compile Include="GlobalSuppressions.cs" />
523+
<Compile Include="Host\UserTypeScanner.cs" />
523524
<Compile Include="Host\PrimaryHostCoordinator.cs" />
524525
<Compile Include="Host\IScriptHostEnvironment.cs" />
525526
<Compile Include="Host\NullScriptHostEnvironment.cs" />

0 commit comments

Comments
 (0)