How do I create a CSharpCompilation using Roslyn in Blazor WebAssembly?
Asked Answered
M

1

2

I am trying to write a Blazor WebAssembly (WASM) app that accepts some code (from some text input field) and compiles the code using Roslyn.

I'm using Roslyn's CSharpCompilation class to create the compilation. Its Create method takes four parameters, one of which is a list of MetadataReferences (aka assembly references). In other (non-blazor) type applications, like a C# console app, you could get these MetadataReferences based on Asssembly Location, like this:

var locatedAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => !string.IsNullOrEmpty(a.Location)).ToArray();
foreach (var assembly in locatedAssemblies) 
{
    MetadataReference reference = MetadataReference.CreateFromFile(assembly.Location);
}

This unfortunately no longer works in Blazor WASM, because the Locations of the assemblies are empty.

I had tried getting assemblies in different ways, like AppDomain.CurrentDomain.GetAssemblies() and Assembly.GetEntryAssembly().GetReferencedAssemblies(), but all had empty Locations. I also tried calling Assembly.Load(), but to no avail.

Does anyone know how to get MetadataReferences in Blazor WASM, or how I would otherwise create a compilation in Blazor WASM? (I'm also aware of MetadataReference.CreateFromStream() that I'll probably need to use, but it still requires the assembly location).

Thanks in advance.

Meilen answered 7/11, 2022 at 18:54 Comment(0)
R
3

I also wanted to compile C# inside a Blazor WASM app and found your question without an answer. After some digging I was able to create a working demo (repo link below.) Basically get the bytes for each assembly with HttpClient and use MetadataReference.CreateFromImage(bytes).

Full basic example repo I created: https://github.com/LostBeard/BlazorWASMScriptLoader

ScriptLoaderService.cs source:

using Microsoft.AspNetCore.Components;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Reflection;

namespace BlazorWASMScriptLoader
{
    // requires "Microsoft.CodeAnalysis.CSharp"
    // can be added via nuget
    public class ScriptLoaderService
    {
        HttpClient _httpClient = new HttpClient();

        public ScriptLoaderService(NavigationManager navigationManager)
        {
            _httpClient.BaseAddress = new Uri(navigationManager.BaseUri);
        }

        async Task<MetadataReference?> GetAssemblyMetadataReference(Assembly assembly)
        {
            MetadataReference? ret = null;
            var assmeblyName = assembly.GetName().Name;
            var assemblyUrl = $"./_framework/{assmeblyName}.dll";
            try
            {
                var tmp = await _httpClient.GetAsync(assemblyUrl);
                if (tmp.IsSuccessStatusCode)
                {
                    var bytes = await tmp.Content.ReadAsByteArrayAsync();
                    ret = MetadataReference.CreateFromImage(bytes);
                }
            }
            catch { }
            return ret;
        }

        public async Task<Assembly?> CompileToDLLAssembly(string sourceCode, string assemblyName = "")
        {
            if (string.IsNullOrEmpty(assemblyName)) assemblyName = Path.GetRandomFileName();
            var codeString = SourceText.From(sourceCode);
            var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp11);
            var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
            var appAssemblies = Assembly.GetEntryAssembly()?.GetReferencedAssemblies().Select(o => Assembly.Load(o)).ToList();
            appAssemblies.Add(typeof(object).Assembly);
            var references = new List<MetadataReference>();
            foreach (var assembly in appAssemblies)
            {
                var metadataReference = await GetAssemblyMetadataReference(assembly);
                if (metadataReference == null)
                {
                    // assembly may be located elsewhere ... handle if needed
                    continue;
                }
                var metadataReferene = metadataReference;
                references.Add(metadataReferene);
            }
            CSharpCompilation compilation = CSharpCompilation.Create(
                assemblyName,
                syntaxTrees: new[] { parsedSyntaxTree },
                references: references,
                options: new CSharpCompilationOptions(
                    OutputKind.DynamicallyLinkedLibrary, 
                    concurrentBuild: false,
                    optimizationLevel: OptimizationLevel.Debug
                )
            );
            using (var ms = new MemoryStream())
            {
                EmitResult result = compilation.Emit(ms);
                if (!result.Success)
                {
                    IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (Diagnostic diagnostic in failures)
                    {
                        Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }
                    return null;
                }
                else
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    var assembly = Assembly.Load(ms.ToArray());
                    return assembly;
                }
            }
        }
    }
}
Rouge answered 7/2, 2023 at 20:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.