c# visual-studio-2017 azure-functions assembly-binding-redirect azure-functions-runtime

c# - Funciones de Azure redireccionamiento de enlace



visual-studio-2017 azure-functions (6)

Acaba de publicar una nueva publicación en el blog que explica cómo solucionar el problema, eche un vistazo:

https://codopia.wordpress.com/2017/07/21/how-to-fix-the-assembly-binding-redirect-problem-in-azure-functions/

En realidad, es una versión modificada del código de JoeBrockhaus, que funciona bien incluso para Newtonsoft.Json.dll

¿Es posible incluir un archivo web.config o app.config en la estructura de carpetas de las funciones azure para permitir redirecciones de enlace de ensamblaje?


Aquí hay una solución alternativa para cuando desea la versión exacta de un conjunto en particular. Con este código, puede implementar fácilmente los ensamblajes que faltan:

public static class AssemblyHelper { //-------------------------------------------------------------------------------- /// <summary> /// Redirection hack because Azure functions don''t support it. /// How to use: /// If you get an error that a certain version of a dll can''t be found: /// 1) deploy that particular dll in any project subfolder /// 2) In your azure function static constructor, Call /// AssemblyHelper.IncludeSupplementalDllsWhenBinding() /// /// This will hook the binding calls and look for a matching dll anywhere /// in the $HOME folder tree. /// </summary> //-------------------------------------------------------------------------------- public static void IncludeSupplementalDllsWhenBinding() { var searching = false; AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // This prevents a if(searching) return null; var requestedAssembly = new AssemblyName(args.Name); searching = true; Assembly foundAssembly = null; try { foundAssembly = Assembly.Load(requestedAssembly); } catch(Exception e) { Debug.WriteLine($"Could not load assembly: {args.Name} because {e.Message}"); } searching = false; if(foundAssembly == null) { var home = Environment.GetEnvironmentVariable("HOME") ?? "."; var possibleFiles = Directory.GetFiles(home, requestedAssembly.Name + ".dll", SearchOption.AllDirectories); foreach (var file in possibleFiles) { var possibleAssembly = AssemblyName.GetAssemblyName(file); if (possibleAssembly.Version == requestedAssembly.Version) { foundAssembly = Assembly.Load(possibleAssembly); break; } } } return foundAssembly; }; } }



Inspirado por la respuesta aceptada, pensé que haría una versión más genérica que también tenga en cuenta las actualizaciones.

Obtiene todos los ensamblajes, los ordena descendiendo para obtener la versión más reciente en la parte superior, y luego devuelve la versión más reciente en resolución. Yo lo llamo yo mismo en un constructor estático.

public static void RedirectAssembly() { var list = AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetName()) .OrderByDescending(a => a.Name) .ThenByDescending(a => a.Version) .Select(a => a.FullName) .ToList(); AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { var requestedAssembly = new AssemblyName(args.Name); foreach (string asmName in list) { if (asmName.StartsWith(requestedAssembly.Name + ",")) { return Assembly.Load(asmName); } } return null; }; }


Primer post de SO, así que disculpas si el formateo está un poco apagado.

Hemos solucionado este problema un par de veces y hemos logrado encontrar una mejor manera de obtener los redireccionamientos obligatorios forzando a MSBUILD a generar un archivo de redireccionamientos vinculantes y luego analizarlo para utilizarlo con la respuesta sugerida anteriormente.

Modifique la configuración del proyecto y agregue un par de objetivos:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> ... <AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects> <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType> ... </PropertyGroup> </Project>

Estas clases aplican los redireccionamientos de enlace utilizando la misma idea que se publicó anteriormente ( share ), excepto en lugar de usar el archivo host.json que lee del archivo de redireccionamientos de enlace generado. El nombre de archivo a usar es de reflexión usando ExecutingAssembly.

using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Xml.Serialization; public static class AssemblyBindingRedirectHelper { private static FunctionRedirectBindings _redirects; public static void ConfigureBindingRedirects() { // Only load the binding redirects once if (_redirects != null) return; _redirects = new FunctionRedirectBindings(); foreach (var redirect in _redirects.BindingRedirects) { RedirectAssembly(redirect); } } public static void RedirectAssembly(BindingRedirect bindingRedirect) { ResolveEventHandler handler = null; handler = (sender, args) => { var requestedAssembly = new AssemblyName(args.Name); if (requestedAssembly.Name != bindingRedirect.ShortName) { return null; } var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken).GetPublicKeyToken(); requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion); requestedAssembly.SetPublicKeyToken(targetPublicKeyToken); requestedAssembly.CultureInfo = CultureInfo.InvariantCulture; AppDomain.CurrentDomain.AssemblyResolve -= handler; return Assembly.Load(requestedAssembly); }; AppDomain.CurrentDomain.AssemblyResolve += handler; } } public class FunctionRedirectBindings { public HashSet<BindingRedirect> BindingRedirects { get; } = new HashSet<BindingRedirect>(); public FunctionRedirectBindings() { var assm = Assembly.GetExecutingAssembly(); var bindingRedirectFileName = $"{assm.GetName().Name}.dll.config"; var dir = Path.Combine(Environment.GetEnvironmentVariable("HOME"), @"site/wwwroot"); var fullPath = Path.Combine(dir, bindingRedirectFileName); if(!File.Exists(fullPath)) throw new ArgumentException($"Could not find binding redirect file. Path:{fullPath}"); var xml = ReadFile<configuration>(fullPath); TransformData(xml); } private T ReadFile<T>(string path) { using (StreamReader reader = new StreamReader(path)) { var serializer = new XmlSerializer(typeof(T)); var obj = (T)serializer.Deserialize(reader); reader.Close(); return obj; } } private void TransformData(configuration xml) { foreach(var item in xml.runtime) { var br = new BindingRedirect { ShortName = item.dependentAssembly.assemblyIdentity.name, PublicKeyToken = item.dependentAssembly.assemblyIdentity.publicKeyToken, RedirectToVersion = item.dependentAssembly.bindingRedirect.newVersion }; BindingRedirects.Add(br); } } } public class BindingRedirect { public string ShortName { get; set; } public string PublicKeyToken { get; set; } public string RedirectToVersion { get; set; } }

Clases Xml para usar para deserializar el archivo de redireccionamiento de enlace generado en algo más fácil de usar. Estos se generaron a partir del archivo de redirecciones de enlace mediante el uso de VS2017 "pegar especial -> pegar xml como clases", así que siéntase libre de rodar el suyo si es necesario.

using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Xml.Serialization; // NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0. [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)] public partial class configuration { [System.Xml.Serialization.XmlArrayItemAttribute("assemblyBinding", Namespace = "urn:schemas-microsoft-com:asm.v1", IsNullable = false)] public assemblyBinding[] runtime { get; set; } } [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")] [System.Xml.Serialization.XmlRootAttribute(Namespace = "urn:schemas-microsoft-com:asm.v1", IsNullable = false)] public partial class assemblyBinding { public assemblyBindingDependentAssembly dependentAssembly { get; set; } } [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")] public partial class assemblyBindingDependentAssembly { public assemblyBindingDependentAssemblyAssemblyIdentity assemblyIdentity { get; set; } public assemblyBindingDependentAssemblyBindingRedirect bindingRedirect { get; set; } } [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")] public partial class assemblyBindingDependentAssemblyAssemblyIdentity { [System.Xml.Serialization.XmlAttributeAttribute()] public string name { get; set; } [System.Xml.Serialization.XmlAttributeAttribute()] public string publicKeyToken { get; set; } [System.Xml.Serialization.XmlAttributeAttribute()] public string culture { get; set; } } [System.SerializableAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:schemas-microsoft-com:asm.v1")] public partial class assemblyBindingDependentAssemblyBindingRedirect { [System.Xml.Serialization.XmlAttributeAttribute()] public string oldVersion { get; set; } [System.Xml.Serialization.XmlAttributeAttribute()] public string newVersion { get; set; } }


Suponiendo que está utilizando la última herramienta de funciones de Visual Studio 2017 (11 de junio), obtuve una solución basada en la configuración bastante razonable para esto siguiendo un fragmento de código publicado por npiasecki en el número 992 .

Sería ideal si esto se gestionara a través del marco, pero al menos teniendo en cuenta la configuración, tiene un poco más de aislamiento de cambios. Supongo que también podría usar algunos pasos de precompilación o plantillas T4 que reconcilien las versiones de los nugets en el proyecto (y sus dependencias) antes de escribir esta configuración o generar código.

Así que la desventaja ..

.. se convierte en tener que recordar actualizar la configuración de BindingRedirects al actualizar el paquete de NuGet ( esto suele ser un problema en app.configs de todos modos ). También puede tener un problema con la solución basada en configuración si necesita redirigir Newtonsoft .

En nuestro caso, utilizamos el nuevo Azure Fluent NuGet que dependía de una versión anterior de Microsoft.IdentityModel.Clients.ActiveDirectory que la versión de las bibliotecas de administración de ARM normales que se usan lado a lado en una función particular.

local.settings.json

{ "IsEncrypted": false, "Values": { "BindingRedirects": "[ { /"ShortName/": /"Microsoft.IdentityModel.Clients.ActiveDirectory/", /"RedirectToVersion/": /"3.13.9.1126/", /"PublicKeyToken/": /"31bf3856ad364e35/" } ]" } } FunctionUtilities.cs

using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Globalization; using System.Linq; using System.Reflection; namespace Rackspace.AzureFunctions { public static class FunctionUtilities { public class BindingRedirect { public string ShortName { get; set; } public string PublicKeyToken { get; set; } public string RedirectToVersion { get; set; } } public static void ConfigureBindingRedirects() { var config = Environment.GetEnvironmentVariable("BindingRedirects"); var redirects = JsonConvert.DeserializeObject<List<BindingRedirect>>(config); redirects.ForEach(RedirectAssembly); } public static void RedirectAssembly(BindingRedirect bindingRedirect) { ResolveEventHandler handler = null; handler = (sender, args) => { var requestedAssembly = new AssemblyName(args.Name); if (requestedAssembly.Name != bindingRedirect.ShortName) { return null; } var targetPublicKeyToken = new AssemblyName("x, PublicKeyToken=" + bindingRedirect.PublicKeyToken) .GetPublicKeyToken(); requestedAssembly.Version = new Version(bindingRedirect.RedirectToVersion); requestedAssembly.SetPublicKeyToken(targetPublicKeyToken); requestedAssembly.CultureInfo = CultureInfo.InvariantCulture; AppDomain.CurrentDomain.AssemblyResolve -= handler; return Assembly.Load(requestedAssembly); }; AppDomain.CurrentDomain.AssemblyResolve += handler; } } }