Подтвердить что ты не робот

Azure Функции, связывающие перенаправление

Можно ли включить файл web.config или app.config в структуру папок azure, чтобы разрешить переадресацию связывания сборки?

4b9b3361

Ответ 1

Предполагая, что вы используете последнюю (июнь'17) Visual Studio 2017 Function Tooling, я получил несколько разумное решение на основе конфигурации для этого, следуя фрагменту кода, отправленному npiasecki поверх Проблема № 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\" } ]"
    }
}
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;
            }
        }
    }

Ответ 3

Вдохновленный принятым ответом, я решил, что сделаю более общий ответ, который также учитывает улучшения.

Он выбирает все сборки, упорядочивает их по убыванию, чтобы получить самую новую версию сверху, а затем возвращает самую новую версию при разрешении. Я называю это в статическом конструкторе сам.

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

Сегодня это невозможно прямо, но мы думаем о путях достижения этого. Не могли бы вы открыть вопрос о https://github.com/Azure/azure-webjobs-sdk-script/issues, чтобы убедиться, что ваш конкретный сценарий рассмотрен? Спасибо!

Ответ 5

Здесь альтернативное решение, когда вы хотите точную версию конкретной сборки. С помощью этого кода вы можете легко развернуть отсутствующие сборки:

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

Ответ 6

Первый ТАК пост, поэтому извиняюсь, если форматирование немного выключено.

Мы несколько раз сталкивались с этой проблемой и смогли найти лучший способ получения требуемых перенаправлений, заставив 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; }
}