Provide the steps required to reproduce the problem:
func CLI from Github releases and .NET Core 3.1 SDK> dotnet --version3.1.401> func --version3.0.2798> mkdir Assembler and > cd Assembler> dotnet new classlib -n Assembler.Lib55 (Create new class library)cd Assembler.Lib55dotnet add package Microsoft.IdentityModel.Protocols.OpenIdConnect --version 5.5.0Lib55Class with following code snippetcsharp
namespace Assembler.Lib55
{
public class Lib55Class
{
public async Task ReturnNothing()
{
var metadataAddress ="https://justformajidstests.b2clogin.com/justformajidstests.onmicrosoft.com/B2C_1_DUMMY/v2.0/.well-known/openid-configuration";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever()
);
var config = await configurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
var signingKeys = config.SigningKeys;
}
}
}
> dotnet new classlib -n Assembler.Lib56 (Create new class library)
cd Assembler.Lib56dotnet add package Microsoft.IdentityModel.Protocols.OpenIdConnect --version 5.6.0Lib56Class with similar code snippetcsharp
namespace Assembler.Lib56
{
public class Lib56Class
{
public async Task ReturnNothing()
{
var metadataAddress ="https://justformajidstests.b2clogin.com/justformajidstests.onmicrosoft.com/B2C_1_DUMMY/v2.0/.well-known/openid-configuration";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever()
);
var config = await configurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
var signingKeys = config.SigningKeys;
}
}
}
> func init Assembler.Functions --dotnet
<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput> to Assembler.Functions.csproj> dotnet add reference ../Assembler.Lib55 and dotnet add reference ../Assembler.Lib56Create a new class Debug.cs with this code snippet
namespace Assembler.Functions
{
public static class Debug
{
[FunctionName(nameof(Debug))]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "debug")] HttpRequest req,
ILogger log)
{
var c55 = new Lib55Class();
await c55.ReturnNothing().ConfigureAwait(false);
//NOTE: If the following line is removed/commented out, the exception may not always happen!
var assembly = Assembly.GetAssembly(typeof(OpenIdConnectConfiguration));
var c56 = new Lib56Class();
await c56.ReturnNothing().ConfigureAwait(false);
return new OkResult();
}
}
}
> func start and invoke API http://localhost:7071/api/debug
HTTP Status code should be 200
HTTP Status is 500 with an exception
System.Private.CoreLib: Exception while executing function: Debug. Assembler.Lib55: Method not found: 'System.Collections.Generic.ICollection`1<Microsoft.IdentityModel.Tokens.SecurityKey> Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration.get_SigningKeys()'.
None
Provide any related information
C#HTTPTriggerBy the way, I also created ASP.NET Core API to verify if the underlying issue is with ASP.NET Core as well or just Azure Functions.
> dotnet new webapi -n Assembler.AspnetCore
And I added the same code in WeatherForecastController that I added to Azure Functions earlier
namespace Assembler.AspnetCore.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> Get()
{
var c55 = new Lib55Class();
await c55.ReturnNothing().ConfigureAwait(false);
//NOTE: If the following line is removed/commented out, the exception may not always happen!
var assembly = Assembly.GetAssembly(typeof(OpenIdConnectConfiguration));
var c56 = new Lib56Class();
await c56.ReturnNothing().ConfigureAwait(false);
return new OkResult();
}
}
}

I couldn't reproduce the same issue in ASP.NET Core 3.1 project
My colleague, Majid (@themajix) created a github repo with more details.
Repo: https://github.com/themajix/mutiversion_assemblies_in_functions
What we have observed so far is


Not sure if FunctionAssemblyLoadContext loads assemblies with its dependencies or just loads the requested assembly?
cc @fabiocav and @brettsam as they are the code owners for FunctionAssemblyLoadContext.
This is an awesome repro, thank you! I'm looking at it now.
Let me post my notes here as I spent quite a while investigating it:
This can happen under the following circumstances:
If 3 and 4 happen in the opposite order, we'll correctly return 5.6.0 first and everything works. So we need a way to make sure we always return that assembly.
I have a prototype fix ready but it'll need some review but it will need some more testing before merging -- I've scheduled this for our next Sprint.
FYI -- In this specific case, you end up with a type mis-match b/c the SigningKeys property returns ICollection<SigningKey> -- and we've already returned Microsoft.IdentityModel.Tokens.SigningKey 5.5.0, yet the Function LoadContext now expects 5.6.0 -- and since the type versions don't match, the methods don't match.
Oh I should note -- tomorrow morning I'm going to investigate a workaround for you. It may involve you force-loading types from these "higher-versioned" assemblies early in your process.
And thank you IMMENSELY for this repro -- this is a complicated scenario and you made it a lot simpler to work with. I really appreciate that.
Thank you so much Brett! We really appreciate how fast you responded to our request. I'm sure that this will make the whole team here really happy, as we have been struggling with this issue for a while now.
To set expectations, this likely won't be released for roughly a month (with the release that begins at the end of our next Sprint). I can, however, provide you with a private site extension that you can test on your own before that time and let me know if it works.
There's also a workaround in the meantime. You need to explicitly load the troublesome assemblies from your Function early, to ensure that the correct version is loaded. You can do this with a FunctionsStartup class like this:
using FunctionTests;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
[assembly: FunctionsStartup(typeof(Startup))]
namespace FunctionTests
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var t = typeof(SecurityKey); // Microsoft.IdentityModel.Tokens
t = typeof(OpenIdConnectConfiguration); // Microsoft.IdentityModel.Protocols.OpenIdConnect
t = typeof(LogHelper); // Microsoft.IdentityModel.Logging
t = typeof(AuthenticationProtocolMessage); // Microsoft.IdentityModel.Protocols
}
}
}
This forced version 5.6.0 of these assemblies to be loaded and everything seemed to work. If you want to take this approach-- let me know and I can help you identify whether there are other assemblies that could lead to this.
Yes, I think we are going to take this approach to minimize the risk of having runtime errors in our production environment. Do you already have a utility to identify potentially problematic assemblies in a project, or will the identification task be a manual one?
We're going to move this to Sprint 85 as there are already 2 other AssemblyLoadContext issues going in this sprint and we'd rather spread them out to minimize any negative impact. The workaround will continue to work in the meantime.
Because we're bundling Sprint 84 and 85 together, we unfortunately have to move this to Sprint 86 now.
PR is in progress. Leaving this assigned to Sprint 86.
Just hit this problem. Downgraded from 6.8.0 (latest of the openid packages) to 5.5.0. That alone did NOT fix the problem. Added the above typeof-code and then it did work.
I was a bit quick in the previous comment. It did in fact always work locally. And it did work in ONE of the test environments we have. Not the most important test environment though, so its a bit more random than expected even.
Not entirely confirmed yet, but I had to make sure all our startup classes had the above "fix" in them. If not then it was a bit random who "won" and what was in effect... At least when I did put the "fix" into both our startup-classes it worked as expected again.
Most helpful comment
This is an awesome repro, thank you! I'm looking at it now.