Azure功能绑定重定向

时间:2016-06-29 08:15:53

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

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

6 个答案:

答案 0 :(得分:20)

假设您使用的是最新的(6月 17)Visual Studio 2017功能工具,我在npiasecki发布的BindingRedirects代码片段之后为此创建了一个有点合理的基于配置的解决方案。 3}}

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

所以缺点......

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

在我们的案例中,我们使用的新Azure Fluent NuGet依赖于旧版{ "IsEncrypted": false, "Values": { "BindingRedirects": "[ { \"ShortName\": \"Microsoft.IdentityModel.Clients.ActiveDirectory\", \"RedirectToVersion\": \"3.13.9.1126\", \"PublicKeyToken\": \"31bf3856ad364e35\" } ]" } } ,而不是在特定函数中并排使用的普通ARM管理库的版本。

local.settings.json
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;
            }
        }
    }
FunctionUtilities.cs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define LINE_BUFFER_LEN   (512)
#define RESERVE_NEWLINDE          0
#define AUTO_FILTER_NEWLINDE      1

typedef int (* LINE_READER)(char * pstrLine, int uiBufferLen, void * pvData);

typedef struct st_HW_SSP_CONFIG
{
    const char * pstrConfigPath;
    LINE_READER pfLineReader;
    FILE * pstFile;
    void * pvData;
    int CurrentLine;
    int Flag;
} CONFIG_ST;

int CloseConfig(CONFIG_ST * pstConfig)
{
    if (!pstConfig)
    {
        // record error
        return -1;
    }
    if (fclose(pstConfig->pstFile))
    {
        // record error
    }
    return 0;
}

int OpenConfigFile(const char * pstrFilePath, CONFIG_ST * pstConfig)
{
    FILE * pstFile = NULL;
    if ((!pstrFilePath) || (!pstConfig))
    {
        return -1;
    }
    pstFile = fopen(pstrFilePath, "r");
    if (!pstFile)
    {
        return -1;
    }
    pstConfig->pstFile = pstFile;
    pstConfig->pstrConfigPath = pstrFilePath;
    pstConfig->Flag = RESERVE_NEWLINDE;
    return 0;
}

int IsNullStr(const char *pcStr)
{
    const char *pcTmp = pcStr;
    while ('\0' != *pcTmp)
    {
        if (!isspace(*pcTmp))
        {
            return 0;
        }

        pcTmp++;
    }
    return 1;
}

int IsEffectiveLine(char acFileLineBuffer[LINE_BUFFER_LEN])
{
    if (0 == strlen(&acFileLineBuffer[0]))
    {
        return 0;
    }
    if ('#' == acFileLineBuffer[0]) // strip as a comment line
    {
        return 0;
    }
    if (IsNullStr(&acFileLineBuffer[0]))
    {
        return 0;
    }
    return 1;
}

void FilterNewLine(char* pcLine,  int MaxNumLen)
{
    int uiLen = strlen(pcLine);
    if (uiLen > 1)
    {
        if ('\n' == pcLine[uiLen - 1])
        {
            pcLine[uiLen - 1] = '\0';

            if (uiLen > 2)
            {
                if ('\r' == pcLine[uiLen - 2])
                {
                    pcLine[uiLen - 2] = '\0';
                }
            }
        }
    }
    return;
}

int ReadConfigFile(CONFIG_ST * pstConfig)
{
    char acFileLineBuffer[LINE_BUFFER_LEN] = {0};
    char * pstrRead = NULL;
    int Ret = 0;

    if (!pstConfig)
    {
        return -1;
    }
    if ((!pstConfig->pstFile) || (!pstConfig->pfLineReader))
    {
        return -1;
    }

    rewind(pstConfig->pstFile);
    pstConfig->CurrentLine = 0;

    do
    {
        memset((void *)&acFileLineBuffer[0], 0, LINE_BUFFER_LEN);
        pstrRead = fgets(&acFileLineBuffer[0], LINE_BUFFER_LEN - 1, pstConfig->pstFile);
        if (pstrRead)
        {
            pstConfig->CurrentLine ++;

            if (0 == IsEffectiveLine(acFileLineBuffer))
            {
                continue;
            }

            if (AUTO_FILTER_NEWLINDE == pstConfig->Flag)
            {
                FilterNewLine(acFileLineBuffer, LINE_BUFFER_LEN - 1);
            }

            if (pstConfig->pfLineReader)
            {
                Ret = pstConfig->pfLineReader(&acFileLineBuffer[0],
                                              LINE_BUFFER_LEN,
                                              pstConfig->pvData);

                if (Ret)
                {
                    break;
                }
            }
        }
    }
    while (pstrRead);
    return Ret;
}

int ReadConfigFileEx(const char * pFilePath,
                     LINE_READER pfReader,
                     void * pData, int Flag)
{
    int Ret = 0;
    CONFIG_ST stConfig = {0};

    Ret = OpenConfigFile(pFilePath, &stConfig);
    if (Ret)
    {
        return Ret;
    }

    stConfig.pfLineReader = pfReader;
    stConfig.pvData = pData;
    stConfig.Flag = Flag;
    Ret = ReadConfigFile(&stConfig);
    CloseConfig(&stConfig);
    return Ret;
}


int StringSplit(char *pcStr, char cFlag,
                char * pstArray[], int MaxNum,
                int *pNum)
{
    char * pcStrTemp = 0;
    unsigned int uiIndex = 0;

    pcStrTemp = pcStr;

    while (pcStrTemp)
    {
        pstArray[uiIndex] = pcStrTemp;
        pcStrTemp = strchr(pcStrTemp, cFlag);
        if (pcStrTemp)
        {
            *pcStrTemp = '\0';
            pcStrTemp ++;
            uiIndex ++;
        }
        if (uiIndex >= MaxNum)
        {
            break;
        }
    }

    if (0 != MaxNum)
    {
        *pNum = uiIndex >= MaxNum ? (MaxNum - 1) : uiIndex;
    }
    else
    {
        *pNum = 0;
    }

    return 0;
}

int MyLineReader(char * pstrLine, int uiBufferLen, void * pvData)
{
    printf("Read line:[%s]\r\n", pstrLine);

    char *pArray[8] = {0};
    int Num = 0;
    int index = 0;
    StringSplit(pstrLine, ' ', pArray, 8, &Num);
    for (index = 0; index <= Num; index ++)
    {
        printf("Get value :[%s]\r\n", pArray[index]);
    }

    return 0;
}

int main(int argc, char * argv[])
{
    int ret = 0;

    if (argc != 2)
    {
        printf("Please input file to read.\r\n");
        return 0;
    }

    ret = ReadConfigFileEx(argv[1], MyLineReader, NULL, AUTO_FILTER_NEWLINDE);
    if (ret)
    {
        printf("Open file error.\r\n");
    }

    return 0;
}

答案 1 :(得分:17)

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

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

它实际上是JoeBrockhaus代码的调整版本,即使对于Newtonsoft.Json.dll也适用

答案 2 :(得分:4)

受到接受的答案的启发,我认为我会做一个更通用的考虑升级的问题。

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

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

答案 3 :(得分:3)

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

答案 4 :(得分:0)

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

Dictionary<string, JsonParametersData>

答案 5 :(得分:0)

第一篇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; }
}