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
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