4

I am trying to resolve a MissingMethodException in a pre-compiled F# Azure Function. The exception is thrown when I call an extension method from FSharp.Data.CssSelectorExtensions.

The function is defined in a .Net Framework 4.6.2 class library. I am using the current versions of FSharp.Core (4.2.3) and FSharp.Data (2.3.3). (I have tried older versions of both but the problem persists.) I have added a binding redirect for FSharp.Core per the standard guidance for this kind of problem. The code compiles cleanly but fails when it is executed. It also fails if I attempt to invoke the extension method directly as a simple static method.

Any guidance on how I can get rid of this exception would be much appreciated!

Function Code

module httpfunc

open System.Net
open System.Net.Http
open Microsoft.Azure.WebJobs.Host
open FSharp.Data
open FSharp.Data.CssSelectorExtensions

let Run(req: HttpRequestMessage, log: TraceWriter) =
    async {
        let doc = HtmlDocument.Load("https://google.com")
        let node = doc.CssSelect("div.ctr-p") // <-- method is missing
        return req.CreateResponse(HttpStatusCode.OK)
    } |> Async.RunSynchronously

Exception Message

 mscorlib: Exception while executing function: Functions.httpfunc. 
 mscorlib: Exception has been thrown by the target of an invocation. 
 fsfuncs: Method not found: 'Microsoft.FSharp.Collections.FSharpList`1<FSharp.Data.HtmlNode> CssSelectorExtensions.CssSelect(FSharp.Data.HtmlDocument, System.String)'.

app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.4.1.0" newVersion="4.4.1.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="FSharp.Core" version="4.2.3" targetFramework="net462" />
  <package id="FSharp.Data" version="2.3.3" targetFramework="net462" />
  ...
</packages>

.fsproj

<Project ToolsVersion="15.0" ... />
  <PropertyGroup>
    <RootNamespace>fsfuncs</RootNamespace>
    <AssemblyName>fsfuncs</AssemblyName>
    <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
    <TargetFSharpCoreVersion>4.4.1.0</TargetFSharpCoreVersion>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <Name>fsfuncs</Name>
  </PropertyGroup>
  ...
</Project>

Edit

On Fyodor Soikin's advice I've determined that multiple versions of FSharp.Core.dll are being loaded: one from the GAC and one from the NuGet packages folder.

Two versions of FSharp.Core.dll are loaded.

John Hoerr
  • 7,955
  • 2
  • 30
  • 40
  • I would run it under debugger and see which modules get loaded, and whether you get two differently versioned copies of `FSharp.Core`. – Fyodor Soikin Aug 25 '17 at 17:21
  • I'm not sure how to do that, unfortunately -- to the best of my knowledge Visual Studio doesn't yet have debugger support for F# Azure Functions. Also, can you help me understand how I would get two different versions of `FSharp.Core` when there's just a single class library involved? That is, where would the second version come from? – John Hoerr Aug 25 '17 at 17:26
  • 2
    There is a way to run Azure Functions locally, [check it out](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local). Different versions may come from very unexpected places. One obvious option that comes to mind is the GAC. – Fyodor Soikin Aug 25 '17 at 17:28
  • Cool, thanks for that pointer! It does look like two versions of the FSharp.Core.dll are being loaded (see my edit just now.) Is the solution to remove FSharp.Core.dll from the GAC, or can I otherwise prevent it from being loaded from there? – John Hoerr Aug 25 '17 at 17:47
  • This means that your `bindingRedirect` didn't take. I would verify the usual suspects now: is the config in the right place? are the versions correct? etc. – Fyodor Soikin Aug 25 '17 at 17:50
  • The app.config is next to the project file and appears as a dll.config when the project is built. The binding redirect version is 4.4.1.0, which I think is also correct, (though the FSharp.Core versioning is a little confusing...) One data point: the project has `AutoGenerateBindingRedirects=true`. If I change this to _false_ then _only_ the NuGet FSharp.Core.DLL is loaded, and the FSharp.Data.DLL is _not_ loaded. The exception continues to be thrown. – John Hoerr Aug 25 '17 at 17:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/152857/discussion-between-john-hoerr-and-fyodor-soikin). – John Hoerr Aug 25 '17 at 18:07

1 Answers1

2

The execution engine behind Azure Functions already loads FSharp.Core.dll (because it depends on F# compiler services to run your F# scripts) and I think you will always get the version of FSharp.Core.dll that is specified by the execution engine's app.config, which is 4.4.0.0.

I might be missing something, but I think your best chance is to make your function use version 4.4.0.0. Could you try removing the explicit FSharp.Core reference? That way, the runtime should load just the (already pre-loaded) version of FSharp.Core.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks Tomas, this is helpful. I was doing my best to use the packaged `FSharp.Core`, but per Fyodor Soikin's comments it seems that the binding redirect I had in place does not work for class libraries and thus isn't a good fit for the compiled functions. I appreciate the pointer to the execution engine app.config; that'll make it easier to keep my `FSharp.Core` GAC reference in sync with the platform version. – John Hoerr Aug 29 '17 at 02:52