.net-core - 如何在 Blazor WebAssembly 中使用 Roslyn 创建 CSharpCompilation?

标签 .net-core blazor .net-6.0 roslyn blazor-webassembly

我正在尝试编写一个 Blazor WebAssembly (WASM) 应用程序,它接受一些代码(来自某些文本输入字段)并使用 Roslyn 编译代码。

我使用 Roslyn 的 CSharpCompilation 类来创建编译。它的 Create 方法采用四个参数,其中之一是 MetadataReference(也称为程序集引用)列表。在其他(非 blazor)类型的应用程序中,例如 C# 控制台应用程序,您可以根据程序集位置获取这些 MetadataReference,如下所示:

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

不幸的是,这在 Blazor WASM 中不再有效,因为程序集的 Location 为空。

我曾尝试以不同的方式获取程序集,例如 AppDomain.CurrentDomain.GetAssemblies()Assembly.GetEntryAssembly().GetReferencedAssemblies(),但全部都是空的 位置。我也尝试调用 Assembly.Load(),但无济于事。

有谁知道如何在 Blazor WASM 中获取MetadataReference,或者我如何在 Blazor WASM 中创建编译? (我还知道我可能需要使用 MetadataReference.CreateFromStream(),但它仍然需要程序集位置)。

提前致谢。

最佳答案

我还想在 Blazor WASM 应用程序中编译 C#,但发现您的问题没有答案。经过一番挖掘,我能够创建一个工作演示(下面的存储库链接)。基本上使用 HttpClient 获取每个程序集的字节并使用 MetadataReference.CreateFromImage(bytes)。

我创建的完整基本示例存储库:https://github.com/LostBeard/BlazorWASMScriptLoader

ScriptLoaderService.cs 来源:

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

关于.net-core - 如何在 Blazor WebAssembly 中使用 Roslyn 创建 CSharpCompilation?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74351536/

相关文章:

c# - 在 NuGet nuspec 中使用 "dotnet"或 ".NETPlatform5.0"?

c# - EF 核心 6 : How to pluralize class names when running scaffolding

c# - .NET MAUI 标签绑定(bind)未在 C# 中更新 - 不是 MVVM

c# - 带有 Blazor 服务器端组件的 MVC 应用程序中的防伪 token 验证

asp.net-core - 在 Blazor 中更改绑定(bind)到组件的模型时更新 UI

c# - ASP.NET核心6 : cannot resolve symbol 'Ok'

asp.net - 部署阶段/生产应用程序设置 Asp.net Core

c# - 尝试在 Visual Studio Code 中调试 C# 时出现“要执行的应用程序不存在”错误

c# - 将 OperationCanceledException 关联到 CancellationToken 的正确方法

blazor - Fresh Blazor Webassembly 模板 CLI v5.0.100 生成 "Unauthorized"