sdk/Sdk/ExtensionsMetadataEnhancer.cs (84 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Mono.Cecil; namespace Microsoft.Azure.Functions.Worker.Sdk { internal class ExtensionsMetadataEnhancer { private const string ExtensionsBinaryDirectoryPath = @"./.azurefunctions"; private const string AssemblyNameFromQualifiedNameRegex = @"^.+,+\s(.+),\sVersion=.+$"; public static void AddHintPath(IEnumerable<ExtensionReference> extensions) { foreach (ExtensionReference extension in extensions) { string? assemblyName = GetAssemblyNameOrNull(extension.TypeName); if (string.IsNullOrEmpty(extension.HintPath) && !string.IsNullOrEmpty(assemblyName)) { extension.HintPath = $@"{ExtensionsBinaryDirectoryPath}/{assemblyName}.dll"; } } } public static IEnumerable<ExtensionReference> GetWebJobsExtensions(string fileName) { // NOTE: this is an incomplete approach to getting extensions and is intended only for our usages. // Running this with arbitrary assemblies (especially user supplied) can lead to exceptions. AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(fileName); IEnumerable<CustomAttribute> attributes = assembly.Modules.SelectMany(p => p.GetCustomAttributes()) .Where(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Hosting.WebJobsStartupAttribute"); foreach (CustomAttribute attribute in attributes) { CustomAttributeArgument typeProperty = attribute.ConstructorArguments.ElementAtOrDefault(0); CustomAttributeArgument nameProperty = attribute.ConstructorArguments.ElementAtOrDefault(1); TypeDefinition typeDef = (TypeDefinition)typeProperty.Value; string assemblyQualifiedName = Assembly.CreateQualifiedName( typeDef.Module.Assembly.FullName, GetReflectionFullName(typeDef)); string name = GetName((string)nameProperty.Value, typeDef); yield return new ExtensionReference { Name = name, TypeName = assemblyQualifiedName, HintPath = $@"{ExtensionsBinaryDirectoryPath}/{Path.GetFileName(fileName)}", }; } } private static string? GetAssemblyNameOrNull(string? typeName) { if (typeName == null) { return null; } var match = Regex.Match(typeName, AssemblyNameFromQualifiedNameRegex); if (match is {Success: true, Groups.Count: 2}) { return match.Groups[1].Value; } return null; } // Copying the WebJobsStartup constructor logic from: // https://github.com/Azure/azure-webjobs-sdk/blob/e5417775bcb8c8d3d53698932ca8e4e265eac66d/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsStartupAttribute.cs#L33-L47. private static string GetName(string name, TypeDefinition startupTypeDef) { if (string.IsNullOrEmpty(name)) { // for a startup class named 'CustomConfigWebJobsStartup' or 'CustomConfigStartup', // default to a name 'CustomConfig' name = startupTypeDef.Name; int idx = name.IndexOf("WebJobsStartup"); if (idx < 0) { idx = name.IndexOf("Startup"); } if (idx > 0) { name = name.Substring(0, idx); } } return name; } private static string GetReflectionFullName(TypeReference typeRef) { return typeRef.FullName.Replace("/", "+"); } } }