azure-functions-host: Could not load file or assembly 'Microsoft.CodeAnalysis'

So I built a simple script runner class to take an arbitrary block of C# code and run it by using Roslyns scripting API’s. This functionality works everywhere except in my azure functions app for some reason.

    public static class Execute
    {
        [FunctionName("Execute")]
        public static async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "Execute")]HttpRequestMessage req)
        {
            var json = await req.Content.ReadAsStringAsync();
            var request = (json.Contains("$type")
                ? JsonConvert.DeserializeObject(json, ObjectExtensions.JSONSettings)
                : JsonConvert.DeserializeObject<ScriptRequest<dynamic>>(json)) as ScriptRequest;

            return await new ScriptRunner(Log).Run(request.code, request.Imports, request.args, Log);
        }

        void Log(LogLevel level, string message) => Console.WriteLine(message);
    }

Looking at the logged output from the ctor in the following “ScriptRunner” class I see that it outputs …

Loaded: Microsoft.CodeAnalysis.CSharp.Scripting, Version=3.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

Then I get:

Could not load file or assembly ‘Microsoft.CodeAnalysis, Version=3.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’. The system cannot find the file specified.

I’m using the latest version from nuget (without ticking the include previews option) of all dependencies.

So given that it can both see it but not see it at the same time and as a console app this runs fine I figure this must a functions runtime issue?

using Core.Objects;
using Core.Objects.Dtos.Workflow;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Workflow
{
    internal class ScriptRunner : IScriptRunner
    {
        internal static Assembly[] references;

        public ScriptRunner(LogEvent log)
        {
            try
            {
                if (references == null)
                {
                    // some of the stack might not have been loaded, lets make sure we load everything so we can give a complete response
                    // first grab what's loaded
                    var loadedAlready = AppDomain.CurrentDomain
                            .GetAssemblies()
                            .Where(a => !a.IsDynamic)
                            .ToList();

                    // then grab the bin directory
                    var thisAssembly = Assembly.GetExecutingAssembly();
                    var binDir = thisAssembly.Location
                        .Replace(thisAssembly.ManifestModule.Name, "");
                    log(WorkflowLogLevel.Debug, $"Bin Directory: {binDir}");

                    // from the bin, grab our core dll files
                    var stackDlls = Directory.GetFiles(binDir)
                        //.Select(i => i.ToLowerInvariant())
                        .Where(f => f.EndsWith("dll"))
                        .ToList();

                    loadedAlready.ForEach(a => log(WorkflowLogLevel.Info, $"Loaded: {a.FullName} "));

                    // load the missing ones
                    var toLoad = stackDlls
                        .Where(assemblyPath => loadedAlready.All(a => a.CodeBase.ToLowerInvariant() != assemblyPath.ToLowerInvariant()))
                        .Where(a => !a.Contains("api-ms-win"))
                        .ToArray();
                    foreach (var assemblyPath in toLoad)
                    {
                        try 
                        {
                            var a = Assembly.LoadFile(assemblyPath);
                            loadedAlready.Add(a);
                            log(WorkflowLogLevel.Info, $"Loaded: {a.FullName} ");
                        } 
                        catch (Exception ex) 
                        {
                            log(WorkflowLogLevel.Warning, $"Unable to load assembly {assemblyPath} because: " + ex.Message); 
                        }
                    }

                    references = loadedAlready.ToArray();
                }
            }
            catch (Exception ex)
            {
                log(WorkflowLogLevel.Warning, "Script Runner may not have everything it needs, continuing anyway despite exception:\n" + ex.Message);
            }
        }

        public async Task<T> BuildScript<T>(string code, string[] imports, Action<WorkflowLogLevel, string> log)
        {
            try
            {
                var referencesNeeded = references.Where(r => r.GetExportedTypes().Any(t => imports.Contains(t.Namespace)));
                var options = ScriptOptions.Default
                    .AddReferences(referencesNeeded)
                    .WithImports(imports);

                if (log != null)
                {
                    var message = $"\nImports\n  {string.Join("\n  ", imports)}\n\nReferences Needed\n  {string.Join("\n  ", referencesNeeded.Select(r => r.FullName))}";
                    log(WorkflowLogLevel.Debug, message);
                }

                return await CSharpScript.EvaluateAsync<T>(code, options);
            }
            catch (Exception ex)
            {
                log(WorkflowLogLevel.Error, "Script failed to compile.");
                log(WorkflowLogLevel.Error, ex.Message);

                if (ex is CompilationErrorException cEx)
                    log(WorkflowLogLevel.Error, $"Source of the problem:\n{cEx.Source}");

                return default;
            }
        }

        public async Task<T> Run<T>(string code, string[] imports, object args, Action<WorkflowLogLevel, string> log)
        {
            try
            {
                var referencesNeeded = references.Where(r => r.GetExportedTypes().Any(t => imports.Contains(t.Namespace)));
                var options = ScriptOptions.Default
                    .AddReferences(referencesNeeded)
                    .WithImports(imports);

                if (log != null)
                {
                    var message = $"\nImports\n  {string.Join("\n  ", imports)}\n\nReferences Needed\n  {string.Join("\n  ", referencesNeeded.Select(r => r.FullName))}";
                    log(WorkflowLogLevel.Debug, message);
                }

                return await CSharpScript.EvaluateAsync<T>(code, options, args, args.GetType());
            }
            catch (NullReferenceException ex)
            {
                var typeAndCall = $"(({ex.TargetSite.DeclaringType.Name})object).{ex.TargetSite.Name}";
                var data = new List<string>();

                foreach (var k in ex.Data.Keys) data.Add($"{k}: {ex.Data[k]}");

                throw new Exception(ex.Message + $"\nContext: {ex.Source}\nTarget: {typeAndCall}\n{string.Join("\n", data)}");
            }
            catch (CompilationErrorException ex)
            {
                throw new Exception($"Compilation failed:\n{ex.Message}\n{string.Join(Environment.NewLine, ex.Diagnostics)}");
            }
        }

        public Task Run(string code, string[] imports, object args, Action<WorkflowLogLevel, string> log)
            => Run<bool>(code + ";return true;", imports, args, log);
    }
}

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 1
  • Comments: 28 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Well, good news is that I’m now able to repro – I’ll need to do some more debugging tomorrow to see what’s happening in your case. I wonder if something your workflow execution is doing is causing the load context to change (that could explain why I wasn’t able to repro in my sample).

Are you using Functions v2 or v3? I’m able to repro against v2 – and if I switch to using <PackageReference Include="Microsoft.CodeAnalysis.Scripting" Version="3.5.0" />, it works. Are you able to use that package?

I suspect there’s something going on with our assembly unification code here – I’ll keep investigating.