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 package
await pyodide.loadPackage("pandas");
// Import necessary modules
pyodide.runPython(`import json`);
try {
// Execute the Python code
pyodide.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 result
const resultJson = pyodide.globals.get('_result_json');
return resultJson;
} catch (error) {
// Extract and throw only the relevant error message
const 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
runPythonScript
function 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
CodeEditor
component is used to embed the Monaco Editor. - The
Language
property is set to "python" for Python syntax highlighting. - The
Height
andWidth
properties 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 messages
isRunning = true;
StateHasChanged();// Get Python code from Monaco
var pythonCode = await MonacoCodeEditor.GetCodeAsync();// Call JS to execute Python via Pyodide and get JSON
var json = await JSRuntime.InvokeAsync<string>("runPythonScript", pythonCode);// Parse and flatten JSON into a table-friendly format
dataRows = 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 message
Message = "The Python code must return a valid Pandas DataFrame. Please ensure your function returns a DataFrame.";
}else
{// Display the original error message for other errors
Message = jsEx.Message;}}catch (Exception ex)
{// Handle other errors
Message = ex.Message;}finally
{isRunning = false;
StateHasChanged();}}
Key points:
- The Python code is retrieved from the Monaco Editor using
MonacoCodeEditor.GetCodeAsync()
. - The
runPythonScript
JavaScript 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.