简体   繁体   中英

System.IO.FileNotFoundException when using CSharpScript in Blazor wasm

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

I am afraid this exception is raised because you are trying to access a file on the client disk. This is not allowed in JavaScript, and thus, not allowed in Blazor WebAssembly as well.

Also, the file is searched for on the disk, but in Blazor assemblies are not stored on disk...

Hope this helps...

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM