【问题标题】:Azure Functions binding redirectAzure Functions 绑定重定向
【发布时间】:2016-10-31 19:54:25
【问题描述】:

是否可以在 azure 函数文件夹结构中包含 web.config 或 app.config 文件以允许程序集绑定重定向?

【问题讨论】:

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


    【解决方案1】:

    第一个 SO 帖子,如果格式有点不对,请见谅。

    我们已经多次遇到此问题,并设法通过强制 MSBUILD 生成绑定重定向文件,然后解析该文件以与先前建议的答案一起使用,从而找到获得所需重定向的更好方法。

    修改项目设置并添加几个目标:

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

    这些类使用之前发布的相同想法 (link) 应用绑定重定向,除了使用从生成的绑定重定向文件中读取的 host.json 文件。要使用的文件名来自使用 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; }
        }
    

    用于将生成的绑定重定向文件反序列化为更易于使用的 XML 类。这些是通过使用 VS2017“粘贴特殊 -> 将 xml 粘贴为类”从绑定重定向文件生成的,因此如果需要,请随意滚动。

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

    【讨论】:

      【解决方案2】:

      受到公认答案的启发,我想我会做一个更通用的,同时考虑到升级。

      它获取所有程序集,按降序排列以获取最新版本,然后在解析时返回最新版本。我自己在静态构造函数中调用它。

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

      【讨论】:

      • OrderByDescending(a => a.FullName) 在比较 9.0.0 版本和 10.0.0 版本时可能会遇到问题。
      • 谢谢@wz366。我编辑了我的代码以按版本号正确排序,
      【解决方案3】:

      当您需要特定程序集的确切版本时,这是另一种解决方案。使用此代码,您可以轻松部署缺少的程序集:

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

      【讨论】:

        【解决方案4】:

        刚刚发布了一篇解释如何解决问题的新博文,请看:

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

        它实际上是 JoeBrockhaus 代码的调整版本,即使对于 Newtonsoft.Json.dll 也能正常工作

        【讨论】:

        • 这似乎无法解决它以使用 DocumentDB 数据绑定对对象进行反序列化... 尝试反序列化的数据绑定在以某种方式调用重定向逻辑之前被调用。
        • 真的。队列触发器也有同样的问题,解决方法是在设置绑定重定向后将触发器的数据类型设置为“String”并在代码中自行反序列化。
        • 基于 akazemis 的博客,我添加了一些防止失败的保护措施,因为这段代码运行得很早,很难分析异常。在 Startup `try { AssemblyBindingRedirectHelper.ConfigureBindingRedirects(); IsStarted = true; } catch (Exception ex) { StartError = ex.Message + " " + ex.StackTrace; }` 静态存储 StartError 然后,在
        【解决方案5】:

        假设您使用的是最新的(2017 年 6 月)Visual Studio 2017 函数工具,我根据npiaseckiIssue #992 上发布的代码的 sn-p 得出了一个稍微合理的基于配置的解决方案。

        如果这是通过框架管理的,那将是理想的,但至少是配置驱动的,你有更多的变更隔离。我想您还可以在写出此配置或生成代码之前使用一些预构建步骤或 T4 模板来协调项目中的 nugets 版本(及其依赖项)。

        所以不利的一面..

        .. 更新 NuGet 包时必须记住更新 BindingRedirects 配置(这通常是 app.configs 中的问题)。如果您需要重定向Newtonsoft,您可能还会遇到配置驱动解决方案的问题。

        在我们的例子中,我们使用的是新的 Azure Fluent NuGet,它依赖于旧版本的 Microsoft.IdentityModel.Clients.ActiveDirectory,而不是在特定函数中并排使用的普通 ARM 管理库的版本。

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

        【讨论】:

        • 谢谢。这对于大多数 nuget 项目来说是非常必要的。希望他们能尽快解决这个问题。
        • 对于任何努力完成这项工作的人:如果您有超过 1 个版本需要重新加载,则必须删除行 AppDomain.CurrentDomain.AssemblyResolve -= handler;因为这意味着只有它找到的第一个版本被重定向。
        • 重定向 Newtonsoft 有什么问题?
        • @NicholasJ.Markkula 此示例中的问题源于这样一个事实,即确定要重定向的内容的代码使用的是 Newtonsoft。因此,如果调用代码的 AppDomain 已经加载了 Newtonsoft 的 9.x 版本,但是 json 文件想要重定向到 10.x,那将不起作用,因为 9.x 已经加载到 AppDomain 中,因此无法重定向首次加载时。
        • 我们实现了这一点。它在我的机器上工作,但在另一个人的机器上,他得到了 StackOverflowException。微软说你should not call Assembly.Load from your handler 因为它可能导致堆栈溢出。有没有其他选择?
        【解决方案6】:

        今天还不能直接实现,但我们正在考虑实现这一目标的方法。您能否在https://github.com/Azure/azure-webjobs-sdk-script/issues 上打开一个问题以确保查看您的特定场景?谢谢!

        【讨论】:

        猜你喜欢
        • 2018-01-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-31
        相关资源
        最近更新 更多