4/28/2025 Alan Robertson
Building a Dynamic Python Execution Environment with .NET MAUI, Pyodide, Monaco, and Radzen
Introduction and Overview
In this blog post, we will explore how to build a dynamic Python execution environment using .NET MAUI. This project allows users to write Python code in a Monaco Editor, execute it in the browser using Pyodide, and display the results in a Radzen DataGrid. The application is designed to provide a seamless experience for running Python scripts, with a focus on data manipulation using Pandas.
Key features of this project include:
- A rich code editor powered by Monaco.
- Python execution in the browser using Pyodide.
- Data visualization using Radzen DataGrid.
- User-friendly error handling for Python execution.
Requirements and Setup
To get started, ensure you have the following prerequisites:
- .NET 9 SDK: Required to build and run the .NET MAUI project.
- Modern Web Browser: Pyodide runs in the browser, so a modern browser is essential.
- Radzen Blazor Components: Used for UI elements like buttons and data grids.
Clone the repository and set up the project:
- Clone the repository: https://github.com/BlazorData-Net/DynamicPythonDotNet/tree/main
- Build and run the project
Implementing Pyodide
Pyodide is a Python runtime for the browser, enabling Python code execution in a web environment. In this project, Pyodide is used to execute Python scripts and return results in JSON format.
The implementation is defined in index.html:
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.1/full/pyodide.js"></script><script>let pyodideReadyPromise = loadPyodide();async function runPythonScript(pythonCode) {let pyodide = await pyodideReadyPromise;// Load the Pandas packageawait pyodide.loadPackage("pandas");// Import necessary modulespyodide.runPython(`import json`);try {// Execute the Python codepyodide.runPython(pythonCode + `_result = load_data()if not isinstance(_result, pd.DataFrame):raise TypeError("Must return a DataFrame")_result_json = _result.to_json(orient="records")`);// Retrieve the JSON resultconst resultJson = pyodide.globals.get('_result_json');return resultJson;} catch (error) {// Extract and throw only the relevant error messageconst errorMessage = error.message.split("\n").find(line => line.includes("TypeError: Must return a DataFrame"));throw new Error(errorMessage || "An error occurred while executing the Python code.");}}</script>
Key points:
- Pyodide is initialized using
loadPyodide(). - The
runPythonScriptfunction executes Python code and ensures the result is a Pandas DataFrame. - Errors are handled gracefully, and only relevant error messages are displayed.
Implementing Monaco
The Monaco Editor provides a rich code editing experience with syntax highlighting for Python. In this project, the editor is integrated into the Home.razor file.
<CodeEditor @ref="@MonacoCodeEditor"Language="python"Code="@CurrentScript"Height="300px"Width="100%" />
Key features:
- The
CodeEditorcomponent is used to embed the Monaco Editor. - The
Languageproperty is set to "python" for Python syntax highlighting. - The
HeightandWidthproperties define the editor's dimensions.
The CurrentScript variable is used to manage the code in the editor. A sample script is preloaded when the page initializes:
protected override void OnInitialized(){CurrentScript = SampleScript;}
Executing the Python Code and Displaying Results in Radzen DataGrid
The ExecutePython method in Home.razor handles the execution of Python code and displays the results in a Radzen DataGrid.
private async Task ExecutePython(){try{Message = ""; // Clear any previous error messagesisRunning = true;StateHasChanged();// Get Python code from Monacovar pythonCode = await MonacoCodeEditor.GetCodeAsync();// Call JS to execute Python via Pyodide and get JSONvar json = await JSRuntime.InvokeAsync<string>("runPythonScript", pythonCode);// Parse and flatten JSON into a table-friendly formatdataRows = new List<Dictionary<string, object>>();var parsedArray = JsonNode.Parse(json)?.AsArray();if (parsedArray != null){foreach (var item in parsedArray){var dict = new Dictionary<string, object>();if (item is JsonObject obj){foreach (var prop in obj){dict[prop.Key] = prop.Value?.GetValue<object>() ?? "";}}dataRows.Add(dict);}}}catch (JSException jsEx){// Check if the error message contains "Must return a DataFrame"if (jsEx.Message.Contains("Must return a DataFrame")){// Display a custom error messageMessage = "The Python code must return a valid Pandas DataFrame. Please ensure your function returns a DataFrame.";}else{// Display the original error message for other errorsMessage = jsEx.Message;}}catch (Exception ex){// Handle other errorsMessage = ex.Message;}finally{isRunning = false;StateHasChanged();}}
Key points:
- The Python code is retrieved from the Monaco Editor using
MonacoCodeEditor.GetCodeAsync(). - The
runPythonScriptJavaScript function is invoked to execute the code. - The JSON result is parsed and displayed in a Radzen DataGrid.
The Radzen DataGrid is defined as follows:
@if (dataRows?.Any() == true){<RadzenDataGrid Data="@dataRows" TItem="Dictionary<string, object>"><Columns>@foreach (var key in dataRows[0].Keys){<RadzenDataGridColumn TItem="Dictionary<string, object>" Title="@key"><Template Context="context">@context[key]</Template></RadzenDataGridColumn>}</Columns></RadzenDataGrid>}
Conclusion
This project demonstrates how to integrate Pyodide, Monaco, and Radzen into a .NET MAUI application to create a dynamic Python execution environment. By combining these technologies, you can build powerful tools for data analysis and visualization directly in the browser.
