You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为C#应用构建类浏览器的带智能提示脚本控制台?适配DI

Great question! Building a scriptable console with IntelliSense that hooks into your C# app's runtime state and DI container is totally feasible—here's a practical, WPF-focused breakdown to make it happen:

1. Core Technology Foundation: Roslyn Scripting

The backbone of your console will be Microsoft Roslyn, .NET's official compiler-as-a-service framework. It lets you dynamically compile and execute C# code at runtime, supports modern C# syntax (including async/await), and integrates seamlessly with your app's existing assemblies. This is way more robust than older alternatives like CodeDOM.

2. DI-Aware Script Execution

To let scripts interact with your app's services, you need to expose your DI container (or specific services) to the script context. Here's a clean way to do it:

  • Create a ScriptGlobals class that holds references to your DI provider or individual services. This acts as the "bridge" between your app and the script.
  • Pass this ScriptGlobals instance when executing scripts, so the script can call methods that leverage your app's injected dependencies.

Example ScriptGlobals and engine setup:

using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

public class ScriptGlobals
{
    private readonly IServiceProvider _serviceProvider;

    public ScriptGlobals(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    // Expose app-specific actions to scripts
    public void OpenMenu()
    {
        var menuService = _serviceProvider.GetRequiredService<IMenuService>();
        menuService.OpenMainMenu();
        WriteToConsole("Main menu opened successfully");
    }

    // Async method example
    public async Task<string> FetchUserAsync(int userId)
    {
        var userService = _serviceProvider.GetRequiredService<IUserService>();
        var user = await userService.GetUserByIdAsync(userId);
        return user?.DisplayName ?? "User not found";
    }

    // Helper to write to the console output
    public void WriteToConsole(string message)
    {
        // Hook this up to your WPF output UI later
        Console.WriteLine($"[Script Output]: {message}");
    }
}

public class AppScriptEngine
{
    private readonly ScriptGlobals _globals;
    private readonly ScriptOptions _scriptOptions;

    // Inject your app's IServiceProvider into the engine
    public AppScriptEngine(IServiceProvider serviceProvider)
    {
        _globals = new ScriptGlobals(serviceProvider);
        // Configure script references/imports to match your app's dependencies
        _scriptOptions = ScriptOptions.Default
            .AddReferences(typeof(IMenuService).Assembly, typeof(IUserService).Assembly)
            .AddImports("System", "System.Threading.Tasks", "YourAppNamespace.Services");
    }

    // Execute scripts asynchronously
    public async Task<object> RunScriptAsync(string scriptCode)
    {
        try
        {
            var script = CSharpScript.Create(scriptCode, _scriptOptions, typeof(ScriptGlobals));
            var executionResult = await script.RunAsync(_globals);
            return executionResult.ReturnValue ?? "Script executed successfully";
        }
        catch (Exception ex)
        {
            return $"Error: {ex.Message}\n{ex.StackTrace}";
        }
    }
}
3. Adding IntelliSense

For a Chrome/Firefox-like experience, you'll need real-time code completion. For WPF, AvalonEdit (a mature open-source text editor control) is your best bet—it supports syntax highlighting, line numbers, and extensible IntelliSense. Combine it with Roslyn's semantic analysis:

  • Use Roslyn's AdhocWorkspace to analyze the current script text as the user types.
  • Fetch completion suggestions via Roslyn's CompletionService and display them in AvalonEdit's CompletionWindow.

Simplified IntelliSense setup for AvalonEdit:

using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.CodeCompletion;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;

private async void ScriptEditor_TextChanged(object sender, EventArgs e)
{
    var editor = (TextEditor)sender;
    var caretPosition = editor.CaretOffset;
    var scriptText = editor.Document.Text;

    // Create a temporary Roslyn workspace to analyze the script
    var workspace = new AdhocWorkspace();
    var project = workspace.AddProject("ScriptProject", LanguageNames.CSharp)
        .AddMetadataReferences(_scriptEngine._scriptOptions.References);
    var docId = workspace.AddDocument(project.Id, "TempScript.cs", scriptText);
    var roslynDoc = workspace.GetDocument(docId);

    // Get completion suggestions from Roslyn
    var completionService = CompletionService.GetService(roslynDoc);
    var completionList = await completionService.GetCompletionsAsync(roslynDoc, caretPosition);

    // Show completion window if there are suggestions
    if (completionList?.Items.Any() == true)
    {
        var completionWindow = new CompletionWindow(editor.TextArea);
        var completionData = completionWindow.CompletionList.CompletionData;
        
        foreach (var item in completionList.Items)
        {
            completionData.Add(new CompletionData(item.DisplayText, item.DisplayText, null));
        }
        
        completionWindow.Show();
    }
}
4. WPF Console UI

Build a simple, intuitive UI with these components:

  • An AvalonEdit control for script input (with syntax highlighting set to C#).
  • A read-only TextBox or ListBox for output (to display script results/errors).
  • A "Run" button (or support Ctrl+Enter) to trigger script execution.

Example XAML:

<Window x:Class="YourApp.ScriptConsole"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
        Title="App Script Console" Height="600" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- Output Panel -->
        <TextBox Grid.Row="0" 
                 Name="OutputBox" 
                 IsReadOnly="True" 
                 VerticalScrollBarVisibility="Auto"
                 Margin="5"/>
        
        <!-- Script Input Editor -->
        <avalonEdit:TextEditor Grid.Row="1" 
                               Name="ScriptEditor" 
                               SyntaxHighlighting="C#"
                               ShowLineNumbers="True"
                               Margin="5"
                               Height="200"/>
        
        <!-- Run Button -->
        <Button Grid.Row="2" 
                Content="Run Script (Ctrl+Enter)" 
                Click="RunScript_Click"
                Margin="5"
                HorizontalAlignment="Right"/>
    </Grid>
</Window>

And the code-behind to tie it all together:

public partial class ScriptConsole : Window
{
    private readonly AppScriptEngine _scriptEngine;

    public ScriptConsole(AppScriptEngine scriptEngine)
    {
        InitializeComponent();
        _scriptEngine = scriptEngine;
        
        // Enable Ctrl+Enter to run scripts
        ScriptEditor.PreviewKeyDown += (s, e) =>
        {
            if (e.Key == Key.Enter && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                RunScript_Click(null, null);
                e.Handled = true;
            }
        };
    }

    private async void RunScript_Click(object sender, RoutedEventArgs e)
    {
        var scriptCode = ScriptEditor.Text;
        if (string.IsNullOrWhiteSpace(scriptCode)) return;

        OutputBox.AppendText($"[Running]: {scriptCode}\n");
        var result = await _scriptEngine.RunScriptAsync(scriptCode);
        OutputBox.AppendText($"[Result]: {result}\n\n");
        
        // Scroll to bottom of output
        OutputBox.ScrollToEnd();
    }
}
5. Key Considerations
  • Security: Running arbitrary C# code is risky. Restrict access to trusted users only, and avoid exposing sensitive services via ScriptGlobals. You could also use AssemblyLoadContext to isolate script execution.
  • Performance: Roslyn's script compilation has overhead—cache frequently used scripts if possible.
  • Error Handling: Add robust error parsing to display user-friendly messages in the output panel.

Putting it all together, you'll have a fully functional console that lets users interact with your app's runtime state, use DI services, and get real-time code suggestions—just like browser dev tools.

内容的提问来源于stack exchange,提问作者MoreMag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:34:49