.NET
scripting
application development
software engineering
C#

Adding scripting functionality to .NET applications

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Adding scripting to a .NET application lets users automate repetitive work, customize behavior, and extend the application without recompiling the core program. The hard part is not running text as code. The hard part is deciding what scripts are allowed to touch, how much power they get, and how you keep the host application stable.

Start with the right scripting model

There are three common ways to add scripting to a .NET application:

  • embed C# scripting with Roslyn
  • host an existing scripting language such as PowerShell
  • design a smaller domain-specific language for a narrow task

For most developer-facing tools, C# scripting is the most natural choice because it reuses the .NET type system and libraries your application already knows.

A minimal Roslyn scripting example

Roslyn exposes C# scripting through Microsoft.CodeAnalysis.CSharp.Scripting. A simple host can evaluate a script string and pass in a globals object for controlled access to application state.

csharp
1using System;
2using System.Threading.Tasks;
3using Microsoft.CodeAnalysis.CSharp.Scripting;
4using Microsoft.CodeAnalysis.Scripting;
5
6public class ScriptGlobals
7{
8    public int Value { get; set; }
9}
10
11public static class Program
12{
13    public static async Task Main()
14    {
15        var globals = new ScriptGlobals { Value = 21 };
16
17        var options = ScriptOptions.Default
18            .AddImports("System");
19
20        int result = await CSharpScript.EvaluateAsync<int>(
21            "Value * 2",
22            options,
23            globals
24        );
25
26        Console.WriteLine(result);
27    }
28}

This pattern is useful because scripts do not need unrestricted access to everything in your process. You can expose only the members you want through the globals type and script options.

Expose an API, not your whole internals

The best scriptable applications treat scripting as a plugin surface, not as a shortcut to every private implementation detail. That usually means defining a small host API such as:

  • read selected application data
  • call approved operations
  • write logs or results

For example, you might pass a service object into the script instead of the application's entire dependency container:

csharp
1using System;
2using System.Threading.Tasks;
3using Microsoft.CodeAnalysis.CSharp.Scripting;
4
5public interface IHostApi
6{
7    void Log(string message);
8    int Add(int x, int y);
9}
10
11public sealed class HostApi : IHostApi
12{
13    public void Log(string message) => Console.WriteLine("[script] " + message);
14    public int Add(int x, int y) => x + y;
15}
16
17public sealed class Globals
18{
19    public IHostApi Host { get; init; } = default!;
20}
21
22// Example script:
23// Host.Log("running");
24// Host.Add(2, 3)

That design makes the scripting surface intentional and easier to version over time.

Security matters more than syntax

The biggest architectural mistake is assuming in-process scripting is a security boundary. It is not. If you let untrusted users run arbitrary C# in your process, they can usually do anything your process can do.

That means:

  • trusted automation scripts can run in-process
  • untrusted scripts should run in a separate process or isolated environment

Modern .NET does not provide a simple, strong in-process sandbox for arbitrary code execution. If script trust is unclear, isolation has to be part of the design from the beginning.

Cache and reuse scripts when performance matters

If the same script runs frequently, compiling or reusing a script object can reduce overhead compared with parsing everything from scratch every time. That matters in editors, automation servers, and rule engines where scripts may execute often.

It also helps to place sensible limits around script execution time, memory usage, and I/O permissions if scripts are part of an interactive product.

Common Pitfalls

The most common mistake is exposing far too much host functionality. A small deliberate API is easier to secure, test, and document than a script environment that can reach everywhere.

Another issue is treating scripting as if it were safe for untrusted code by default. In-process execution is powerful, but it is not a secure sandbox.

Teams also underestimate versioning. If scripts depend on internal types that keep changing, every application release risks breaking user automation.

Finally, avoid bolting scripting on without observability. Log script execution, surface errors clearly, and make it obvious which script caused which action.

Summary

  • Scripting can make a .NET application more flexible, automatable, and extensible.
  • Roslyn C# scripting is a strong default when your users are already in the .NET ecosystem.
  • Expose a small host API instead of the application's whole internals.
  • Do not treat in-process scripting as a security sandbox for untrusted code.
  • Plan for script versioning, logging, and performance from the start.

Course illustration
Course illustration

All Rights Reserved.