web-dev-qa-db-fra.com

Redirection de liaison Azure Functions

Est-il possible d'inclure un fichier web.config ou app.config dans la structure de dossiers des fonctions Azure pour autoriser les redirections de liaison avec Assembly?

18
Martin Smyllie

Vient juste de poster un nouveau billet de blog expliquant comment résoudre le problème, regardez:

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

C'est en fait une version modifiée du code de JoeBrockhaus, qui fonctionne bien même pour Newtonsoft.Json.dll 

16
akazemis

Inspiré par la réponse acceptée, je me suis dit que j'en ferais une plus générique, qui prend également en compte les mises à niveau.

Il récupère tous les assemblages, les ordonne par ordre décroissant pour obtenir la version la plus récente, puis renvoie la version la plus récente lors de la résolution. J'appelle cela dans un constructeur statique moi-même.

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;
    };
}
4
Mikael Svenson

Ce n’est pas directement possible aujourd’hui, mais nous réfléchissons aux moyens d’y parvenir. Pouvez-vous ouvrir un numéro sur https://github.com/Azure/azure-webjobs-sdk-script/issues pour vous assurer que votre scénario spécifique est pris en compte? Merci!

2
David Ebbo

Voici une solution alternative pour quand vous voulez la version exacte d'un assemblage particulier. Avec ce code, vous pouvez facilement déployer les assemblys manquants:

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 stack overflow
            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;
        };
    }
}
0
Eric Jorgensen

Premier article SO, donc excuses si le formatage est un peu désactivé.

Nous avons rencontré ce problème à plusieurs reprises et avons réussi à trouver un meilleur moyen d'obtenir les redirections requises en forçant MSBUILD à générer un fichier de redirections de liaison, puis à l'analyse de ce dernier à utiliser avec la réponse précédemment suggérée.

Modifiez les paramètres du projet et ajoutez quelques cibles:

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

Ces classes appliquent les redirections de liaison en utilisant la même idée que celle précédemment affichée ( link ), à la place du fichier Host.json lu à partir du fichier de redirections de liaison généré. Le nom de fichier à utiliser provient d'une réflexion utilisant 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; }
    }

Les classes XML à utiliser pour désérialiser le fichier de redirection de liaison généré en quelque chose de plus facile à utiliser. Celles-ci ont été générées à partir du fichier de redirection de liaison en utilisant VS2017 "coller spécial -> coller xml en tant que classes", alors n'hésitez pas à lancer le vôtre si nécessaire.

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; }
}
0
xsneebsx