实现一个简单的在浏览器运行Dotnet编辑器

简介: 实现一个简单的在浏览器运行Dotnet编辑器

之前已经实现过Blazor在线编译了,现在我们 实现一个简单的在浏览器运行的编辑器,并且让他可以编译我们的C#代码,

技术栈:

Roslyn 用于编译c#代码

monaco](https://github.com/microsoft/monaco-editor)) 用于提供语法高亮和代码的智能提示

WebAssembly在线编译使用场景

问:在浏览器编译有什么用?我可以在电脑编译还可以调试,为什么要在浏览器中去编译代码?

答:对比某些场景,比如一些Blazor组件库,提供一个简单的编辑框,在编辑框中可以编辑组件代码,并且实时看到组件动态渲染效果,这样是不是会提高一些开发效率?或者说在某些学生,可能刚刚入门,还没有开发设备,想着熟悉c#,使用在线编辑是不是更简单?

问:WebAssembly不是打包几十MB吗?那岂不是下载很久?

答: 可以参考这个博客 如何将WebAssembly优化到1MB,Blazor WebAssembly的优化方案。最小可以到1MB,其实并不会很大

问:是否有示例项目?

答:Blazor 在线编辑器 这是一个可以在浏览器动态编译Blazor的编辑器,

创建WebAssembly

实现我们创建一个空的Blazor WebAssembly的项目 ,并且命名为WebEditor 如图所示

然后删除Pages\Index.razor_Imports.razor,App.razor,MainLayout.razor文件

项目添加包引用,将以下代码copy到项目文件中添加引用

<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />

创建Compile.cs,用于编写编译工具,添加以下代码,在这里我们使用Roslyn编译我们的C#代码,并且执行,这里还会提供 Execute方法供js调用


public class Compile
{
   
   
    /// <summary>
    /// 定义需要加载的程序集,相当于项目引用第三方程序集
    /// </summary>
    static List<string> ReferenceAssembly = new(){
   
   
        "/_framework/System.dll",
        "/_framework/System.Buffers.dll",
        "/_framework/System.Collections.dll",
        "/_framework/System.Core.dll",
        "/_framework/System.Linq.Expressions.dll",
        "/_framework/System.Linq.Parallel.dll",
        "/_framework/mscorlib.dll",
        "/_framework/System.Linq.dll",
        "/_framework/System.Console.dll",
        "/_framework/System.Private.CoreLib.dll",
        "/_framework/System.Runtime.dll"
    };

    private static IEnumerable<MetadataReference>? _references;
    private static CSharpCompilation _previousCompilation;

    private static object[] _submissionStates = {
   
    null, null };
    private static int _submissionIndex = 0;

    /// <summary>
    /// 注入的HttpClient
    /// </summary>
    private static HttpClient Http;

    /// <summary>
    /// 初始化Compile
    /// </summary>
    /// <param name="http"></param>
    /// <returns></returns>
    public static void Init(HttpClient http)
    {
   
   
        Http = http;
    }

    [JSInvokable("Execute")]
    public static async Task<string> Execute(string code)
    {
   
   
        return await RunSubmission(code);
    }

    private static bool TryCompile(string source, out Assembly? assembly, out IEnumerable<Diagnostic> errorDiagnostics)
    {
   
   
        assembly = null;

        var scriptCompilation = CSharpCompilation.CreateScriptCompilation(
            Path.GetRandomFileName(),
            CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)
                .WithLanguageVersion(LanguageVersion.Preview)), _references,
            // 默认引用的程序集
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, usings: new[]
            {
   
   
                "System",
                "System.Collections.Generic",
                "System.Console",
                "System.Diagnostics",
                "System.Dynamic",
                "System.Linq",
                "System.Linq.Expressions",
                "System.Text",
                "System.Threading.Tasks"
            }, concurrentBuild: false), // 需要注意,目前由于WebAssembly不支持多线程,这里不能使用并发编译
            _previousCompilation
        );

        errorDiagnostics = scriptCompilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error);
        if (errorDiagnostics.Any())
        {
   
   
            return false;
        }

        using var peStream = new MemoryStream();
        var emitResult = scriptCompilation.Emit(peStream);
        if (emitResult.Success)
        {
   
   
            _submissionIndex++;
            _previousCompilation = scriptCompilation;
            assembly = Assembly.Load(peStream.ToArray());
            return true;
        }

        return false;
    }

    /// <summary>
    /// 执行Code
    /// </summary>
    /// <param name="code"></param>
    /// <returns></returns>
    private static async Task<string> RunSubmission(string code)
    {
   
   
        var diagnostic = string.Empty;
        try
        {
   
   
            if (_references == null)
            {
   
   
                // 定义零时集合
                var references = new List<MetadataReference>(ReferenceAssembly.Count);
                foreach (var reference in ReferenceAssembly)
                {
   
   
                    await using var stream = await Http.GetStreamAsync(reference);

                    references.Add(MetadataReference.CreateFromStream(stream));
                }

                _references = references;
            }

            if (TryCompile(code, out var script, out var errorDiagnostics))
            {
   
   
                var entryPoint = _previousCompilation.GetEntryPoint(CancellationToken.None);
                var type = script.GetType($"{entryPoint.ContainingNamespace.MetadataName}.{entryPoint.ContainingType.MetadataName}");
                var entryPointMethod = type.GetMethod(entryPoint.MetadataName);

                var submission = (Func<object[], Task>)entryPointMethod.CreateDelegate(typeof(Func<object[], Task>));

                // 如果不进行添加会出现超出索引
                if (_submissionIndex >= _submissionStates.Length)
                {
   
   
                    Array.Resize(ref _submissionStates, Math.Max(_submissionIndex, _submissionStates.Length * 2));
                }
                // 执行代码
                _ = await ((Task<object>)submission(_submissionStates));

            }

            diagnostic = string.Join(Environment.NewLine, errorDiagnostics);

        }
        catch (Exception ex)
        {
   
   
            diagnostic += Environment.NewLine + ex;
        }
        return diagnostic;
    }
}

修改Program.cs文件,在这里我们注入了HttpClient,并且传递到了Compile.Init中;


var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient {
   
    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

var app = builder.Build();

// 获取HttpClient传递到初始化编译
Compile.Init(app.Services.GetRequiredService<HttpClient>());

await app.RunAsync();

编写界面

创建wwwroot/index.html 我们将使用monaco创建我们的编辑框,通过引用cdn加载monaco的js

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <title>WebEditor</title>
    <base href="/" />
    <link href="web-editor.css" rel="stylesheet" />
</head>

<body>
    <div>
        <div class="web-editor" id="monaco">
        </div>
        <div class="web-editor console" id="console">
        </div>
        <div class="clear" id="clear">
            清空调试
        </div>
        <div class="run" id="run">
            运行
        </div>
    </div>
    <!-- 设置autostart="false" 将不会自动加载web Assembly程序集 -->
    <script src="_framework/blazor.webassembly.js"></script>
    <script>
        var require = {
    
     paths: {
    
     'vs': 'https://cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs' } };
    </script>
    <script src="https://cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs/loader.js"></script>
    <script src="https://cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs/editor/editor.main.nls.js"></script>
    <script src="https://cdn.masastack.com/npm/monaco-editor/0.34.1/min/vs/editor/editor.main.js"></script>
    <script>
        // 等待dom加载完成
        window.addEventListener('load', function () {
    
    
            // 创建Monaco对象存储在window中
            window.webeditor = monaco.editor.create(document.getElementById('monaco'), {
    
    
                value: `Console.WriteLine("欢迎使用Token在线编辑器");`, // 设置初始值
                language: 'csharp', // 设置monaco 语法提示
                automaticLayout: true, // 跟随父容器大小
                theme: "vs-dark" // 主题
            });

            document.getElementById("run").onclick = () => {
    
    
                // 调用封装的方法将编辑器的代码传入
                execute(window.webeditor.getValue());
            };

            // 清空调试区
            document.getElementById('clear').onclick = () => {
    
    
                document.getElementById("console").innerText = '';
            }

            async function execute(code) {
    
    
                // 使用js互操调用WebEditor程序集下的Execute静态方法,并且发送参数
                code = await DotNet.invokeMethodAsync('WebEditor', 'Execute', code);
                document.getElementById("console").innerText += code;
            }
        })

    </script>
</body>

</html>

创建web-editor.css样式文件


/*通用样式*/
.web-editor {
   
   
    height: 98vh; /*可见高度*/
    width: 50%;/*区块宽度*/
    float: left; 
}

/*运行按钮*/
.run {
   
   
    position: fixed; /*悬浮*/
    height: 23px;
    width: 34px;
    right: 8px; /*靠右上角*/
    cursor: pointer; /*显示手指*/
    background: #3d5fab; /*背景颜色*/
    border-radius: 6px; 
    user-select: none; /*禁止选择*/
}

/*清除按钮*/
.clear {
   
   
    position: fixed;
    height: 23px;
    width: 69px;
    right: 45px;
    cursor: pointer;
    background: #fd0707;
    border-radius: 6px;
    user-select: none;
}

.console {
   
   
    background-color: dimgray;
    color: aliceblue;
}

执行我们的项目,效果如图:

示例地址: GitHub

来着token的分享

相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
目录
相关文章
|
7月前
|
IDE Linux 开发工具
如何在Linux运行RStudio Server并实现Web浏览器远程访问
如何在Linux运行RStudio Server并实现Web浏览器远程访问
222 0
|
Web App开发 前端开发 JavaScript
跨浏览器兼容性:确保您的网站在各个平台上无缝运行
在当今多样化的网络浏览器市场中,确保您的网站能够在不同浏览器上正常运行变得至关重要。跨浏览器兼容性是一项关键任务,涉及到确保您的网站在不同浏览器和设备上都提供一致的用户体验。本博客将深入探讨跨浏览器兼容性的重要性、挑战以及如何实施有效的兼容性策略。
215 0
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
|
5月前
|
JavaScript 前端开发 开发工具
TypeScript的介绍,let age:number = xxx,可以直接看出数据类型,Type由微软开发,可以在任何浏览器和系统中运行,比较适合大型项目,TypeScript的安装
TypeScript的介绍,let age:number = xxx,可以直接看出数据类型,Type由微软开发,可以在任何浏览器和系统中运行,比较适合大型项目,TypeScript的安装
|
2月前
|
NoSQL 前端开发 MongoDB
前端的全栈之路Meteor篇(三):运行在浏览器端的NoSQL数据库副本-MiniMongo介绍及其前后端数据实时同步示例
MiniMongo 是 Meteor 框架中的客户端数据库组件,模拟了 MongoDB 的核心功能,允许前端开发者使用类似 MongoDB 的 API 进行数据操作。通过 Meteor 的数据同步机制,MiniMongo 与服务器端的 MongoDB 实现实时数据同步,确保数据一致性,支持发布/订阅模型和响应式数据源,适用于实时聊天、项目管理和协作工具等应用场景。
|
4月前
|
Rust 安全 JavaScript
Rust 和 WebAssembly 搞大事啦!代码在浏览器中运行,这波操作简直逆天!
【8月更文挑战第31天】《Rust 与 WebAssembly:将 Rust 代码运行在浏览器中》介绍了 Rust 和 WebAssembly 的强大结合。Rust 是一门安全高效的编程语言,而 WebAssembly 则是新兴的网页技术标准,两者结合使得 Rust 代码能在浏览器中运行,带来更高的性能和安全性。文章通过示例代码展示了如何将 Rust 函数编译为 WebAssembly 格式并在网页中调用,从而实现复杂高效的应用程序,同时确保了内存安全性和跨平台兼容性,为开发者提供了全新的可能性。
167 0
|
6月前
|
安全 JavaScript 前端开发
Wasmer 3.0 发布,可在浏览器外运行 WebAssembly
Wasmer 3.0 发布,可在浏览器外运行 WebAssembly
67 2
|
6月前
|
SQL 分布式计算 DataWorks
DataWorks产品使用合集之编辑器中没有飘红的,正常运行可以,带参数运行也可以,但冒烟测试无法运行,是什么导致的
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
Web App开发 测试技术
Selenium+python之不打开浏览器,来运行测试用例
Selenium+python之不打开浏览器,来运行测试用例
251 0