4

I'm trying to use CSharpScript in a Blazor wasm application, testing with a simple EvaluateAsync:

var result = await CSharpScript.EvaluateAsync<int>("1 + 1");

Throws: System.IO.FileNotFoundException: Could not find file "/mscorlib.dll"

I'm using Blazor wasm 3.2.0-preview3.20168.3

Edit: Here's the full code in index.razor:

@code{
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        Console.WriteLine("Initializing...");
        var result = await CSharpScript.EvaluateAsync<int>("1 + 1");
    }
}

And here's the console output:

Initializing...
blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Could not find file "/mscorlib.dll"
System.IO.FileNotFoundException: Could not find file "/mscorlib.dll"
File name: '/mscorlib.dll'
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) <0x3ba83f8 + 0x002b4> in <filename unknown>:0 
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) <0x3b987a0 + 0x0001c> in <filename unknown>:0 
  at System.IO.File.OpenRead (System.String path) <0x3b986d0 + 0x0000a> in <filename unknown>:0 
  at Roslyn.Utilities.FileUtilities.OpenFileStream (System.String path) [0x0001c] in /_/src/Compilers/Core/Portable/FileSystem/FileUtilities.cs:416 
  at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssemblyInternal (System.Reflection.Assembly assembly, Microsoft.CodeAnalysis.MetadataReferenceProperties properties, Microsoft.CodeAnalysis.DocumentationProvider documentation) [0x0005a] in /_/src/Compilers/Core/Portable/MetadataReference/MetadataReference.cs:329 
  at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssemblyInternal (System.Reflection.Assembly assembly) [0x00000] in /_/src/Compilers/Core/Portable/MetadataReference/MetadataReference.cs:271 
  at Microsoft.CodeAnalysis.Scripting.Script.GetReferencesForCompilation (Microsoft.CodeAnalysis.CommonMessageProvider messageProvider, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, Microsoft.CodeAnalysis.MetadataReference languageRuntimeReferenceOpt) [0x0001a] in /_/src/Scripting/Core/Script.cs:252 
  at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScriptCompiler.CreateSubmission (Microsoft.CodeAnalysis.Scripting.Script script) [0x00021] in /_/src/Scripting/CSharp/CSharpScriptCompiler.cs:40 
  at Microsoft.CodeAnalysis.Scripting.Script.GetCompilation () [0x00008] in /_/src/Scripting/Core/Script.cs:144 
  at Microsoft.CodeAnalysis.Scripting.Script`1[T].GetExecutor (System.Threading.CancellationToken cancellationToken) [0x00008] in /_/src/Scripting/Core/Script.cs:361 
  at Microsoft.CodeAnalysis.Scripting.Script`1[T].RunAsync (System.Object globals, System.Func`2[T,TResult] catchException, System.Threading.CancellationToken cancellationToken) [0x0001b] in /_/src/Scripting/Core/Script.cs:465 
  at Microsoft.CodeAnalysis.Scripting.Script`1[T].RunAsync (System.Object globals, System.Threading.CancellationToken cancellationToken) [0x00000] in /_/src/Scripting/Core/Script.cs:439 
  at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync[T] (System.String code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options, System.Object globals, System.Type globalsType, System.Threading.CancellationToken cancellationToken) [0x00000] in /_/src/Scripting/CSharp/CSharpScript.cs:93 
  at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync[T] (System.String code, Microsoft.CodeAnalysis.Scripting.ScriptOptions options, System.Object globals, System.Type globalsType, System.Threading.CancellationToken cancellationToken) [0x00000] in /_/src/Scripting/CSharp/CSharpScript.cs:123 
  at ScriptPlayground.Pages.Index.OnInitializedAsync () [0x0008a] in C:\Users\sarma\source\repos\ScriptPlayground\Pages\Index.razor:17 
  at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x36de968 + 0x0013a> in <filename unknown>:0

Edit 2: After digging deeper into the issue, we traced it down to these lines in Script.cs:

/// <summary>
/// Gets the references that need to be assigned to the compilation.
/// This can be different than the list of references defined by the <see cref="ScriptOptions"/> instance.
/// </summary>
internal ImmutableArray<MetadataReference> GetReferencesForCompilation(
    CommonMessageProvider messageProvider,
    DiagnosticBag diagnostics,
    MetadataReference languageRuntimeReferenceOpt = null)
{
    var resolver = Options.MetadataResolver;
    var references = ArrayBuilder<MetadataReference>.GetInstance();
    try
    {
        if (Previous == null)
        {
            var corLib = MetadataReference.CreateFromAssemblyInternal(typeof(object).GetTypeInfo().Assembly);
            references.Add(corLib);

No matter what options we pass, this will always be called on compilation, MetadataReference.CreateFromAssemblyInternal tries to load a file from disk. So it appears that loading assemblies from disk is hardcoded into the process. We're looking for a clean way of overriding this.

We were already successful in loading assemblies from streams using HttpClient:

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    var name = assembly.GetName().Name + ".dll";
    references.Add(
        MetadataReference.CreateFromStream(
            await this.HttpClient.GetStreamAsync("/_framework/_bin/" + name)));
}

But this doesn't matter as long as CSharpScript is loading another set of assemblies during compilation from disk

4
  • I added the console output Commented May 10, 2020 at 14:17
  • We were able to do something like this, but the issue remains since CSharpScript is loading assemblies using File.OpenRead(path) to load some compilation-time assemblies. Please check the second edit Commented May 11, 2020 at 7:29
  • 1
    That would mean CSharpScript is just not suitable for Blazor. Commented May 12, 2020 at 7:33
  • Yes, that's what I concluded. I'm moving on to compiling the code as an assembly and loading it in the current domain. Commented May 12, 2020 at 18:00

1 Answer 1

2

The CsharpScript API unfortunately has been built with a dependency on looking up assemblies on the local filesystem. If you dig deep enough in the MetadataReference.CreateFromStream source, you will see the culprit being a call to File.OpenRead()

I spent some time myself looking into any possible extensibility of the API where one could supply an alternative assembly resolution strategy. There is a path via the supply of a custom MetaDataReferenceResolver in the ScriptOptions however the baking in of the line you discovered

 var corLib = MetadataReference.CreateFromAssemblyInternal(typeof(object).GetTypeInfo().Assembly);

effectively creates the hard dependency on the file system which kills the idea.

The resolution of mscorlib is done this way above, but then all other asemblies use the Options.MetaDataReferences list. Why I have no idea....

As a pure hack I even had a crack at capturing the call to File.OpenRead() and returing my own stream from HttpClient. The injection works in a desktop process, but something was going wrong in wasm. In the end I gave up too in favour of rolling an alternative such as this approach or this one.

Here was the trial hack for anyone who is interested.

    //Using the Pose library https://github.com/tonerdo/pose
    var client = new HttpClient() { BaseAddress = new Uri(navigationManager.BaseUri) };
    Shim fileShim = Shim.Replace(() => System.IO.File.OpenRead(Is.A<string>())).With(delegate (string assembly)
    {
        var fs = new System.IO.FileStream(assembly, System.IO.FileMode.Open);
        client.GetStreamAsync($"_framework/_bin/{assembly}").Result.CopyTo(fs);
        return fs;
    });

    PoseContext.Isolate(() => Text = CSharpScript.EvaluateAsync<string>("hello").Result, fileShim);

HTH

Sign up to request clarification or add additional context in comments.

2 Comments

We eventually went with a similar approach as well, we're compiling the code in a wrapped method/class inside the assembly and loading it. Fortunately, in our case, we can get away with compiling only once during application startup.
in .Net5 "_framework/_bin" becomes "_framework/"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.