Calling MethodInfo.Invoke Fails in Azure Function Startup

396 Views Asked by At

I have a .Net Core 3.1 application that is implemented as an Azure Function (v3) that fails on startup. In the function app project, I implemented a Startup class that adds the various services to the IServiceCollection collection for dependency injection. I have one assembly that I need to load manually using reflection, and call a method that will add all of the services defined in that assembly to the IServiceCollection collection. As you can see below, it is the line method.Invoke(null, new Object[] { services }); that fails. There doesn't seem to be a problem loading the assembly, getting the type information, or the method information because none of the exceptions are thrown in this code. You can see that I am calling a static method of a static class, hence the null parameter to the Invoke method. I have tried using a non-static class and using the Activator.CreateInstance method to pass an object instance to the Invoke method, but get the same error. This code can execute without error while running locally, and only seems to have a problem once published to the Azure Function resource.

Here are the contents of my Startup.cs file:

using System;
using System.IO;
using System.Reflection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Some.Name.Space.FunctionApp.Startup))]
namespace Some.Name.Space.FunctionApp
{
    public sealed class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            IServiceCollection services = builder.Services;

            var assemblyNamespace = "Some.Name.Space";
            String? baseDirectory = Path.GetDirectoryName(GetType().Assembly.Location);
            if (!String.IsNullOrEmpty(baseDirectory))
            {
                Assembly assembly = Assembly.Load(File.ReadAllBytes(Path.Combine(baseDirectory, $"{assemblyNamespace}.dll")));
                var typeName = $"{assemblyNamespace}.SomeClassName";
                Type? type = assembly.GetType(typeName);
                if (type != null)
                {
                    var methodName = "SomeMethodName";
                    MethodInfo? method = type.GetMethod(methodName);
                    if (method != null)
                    {
                        method.Invoke(null, new Object[] { services });  // THIS IS LINE THAT FAILS.
                    }
                    else
                    {
                        throw new MissingMethodException($"Unable to find the {methodName} method in the {typeName} type.");
                    }
                }
                else
                {
                    throw new TypeLoadException($"Unable to load {typeName} from {assembly.GetName()}.");
                }
            }
            else
            {
                throw new DirectoryNotFoundException("Unable to get the Location directory for the function application.");
            }
        }
    }
}

I know that the problem is not accurately described by the error message below because the entire application runs fine if I simply comment out the one line that fails, and there is plenty of other code that has dependencies to the ''Microsoft.Extensions.DependencyInjection.Abstractions' assembly. Here is a screenshot of the error I see in Azure: enter image description here

1

There are 1 best solutions below

0
Steve Scherrer On

I can't honestly say why my solution works, but I suspect it has something to do with how the Assembly.Load(AssemblyName assemblyRef) method might be using the System.Runtime.Loader.AssemblyLoadContext class. My suspect that my original implementation did not load the correct assembly, even though it was able to load an assembly with the name I was expecting. The solution below has default Version, Culture and PublicKeyToken values for display, so you would need to use the correct values for those properties in your implementation.

Here are the contents of the new Startup.cs file:

using System;
using System.Reflection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Some.Name.Space.FunctionApp.Startup))]
namespace Some.Name.Space.FunctionApp
{
    public sealed class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            IServiceCollection services = builder.Services;
            var assemblyNamespace = "Some.Name.Space";
            AssemblyName assemblyName = new AssemblyName($"{assemblyNamespace}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
            Assembly assembly = Assembly.Load(assemblyName);
            var typeName = $"{assemblyNamespace}.SomeType";
            Type? type = assembly.GetType(typeName);
            if (type != null)
            {
                var methodName = "SomeMethod";
                MethodInfo? method = type.GetMethod(methodName);
                if (method != null)
                {
                    method.Invoke(null, new Object[] { services });
                }
                else
                {
                    throw new MissingMethodException($"Unable to find the {methodName} method in the {typeName} type.");
                }
            }
            else
            {
                throw new TypeLoadException($"Unable to load {typeName} from {assembly.GetName()}.");
            }
        }
    }
}