30

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

4

6 回答 6

30

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

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

所以缺点..

BindingRedirects..更新 NuGet 包时必须记住更新配置(这通常是 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\" } ]"
    }
}
功能实用程序.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;
            }
        }
    }
于 2017-06-08T02:15:34.590 回答
24

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

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

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

于 2017-07-20T13:02:46.947 回答
9

受接受的答案的启发,我想我会做一个更通用的,它也考虑到升级。

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

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;
    };
}
于 2017-09-22T11:44:12.667 回答
3

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

于 2016-06-29T13:44:54.517 回答
2

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

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

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

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

这些类使用之前发布的相同想法(链接)应用绑定重定向,除了使用它从生成的绑定重定向文件中读取的 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; }
}
于 2018-12-15T05:51:18.163 回答
0

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

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;
        };
    }
}
于 2018-05-15T23:55:46.950 回答