How to Safely Execute Dynamic C# Code at Runtime Using Roslyn

Executing dynamic C# code at runtime can be powerful but also comes with security and performance risks. Microsoft’s Roslyn compiler provides a way to compile and execute C# code dynamically while offering safety mechanisms.

This guide walks through how to use Roslyn to safely evaluate and run C# code at runtime.

Why Use Roslyn for Dynamic Code Execution?

Roslyn enables runtime compilation of C# code, making it useful for:

  • Scripting engines within applications.
  • Plugins and extensibility without recompiling the main application.
  • Interactive debugging and testing scenarios.
  • Custom formula evaluations in applications like rule engines.

Step 1: Install Roslyn Dependencies

To use Roslyn for dynamic execution, install the necessary NuGet packages:

Install-Package Microsoft.CodeAnalysis.CSharp.Scripting
Install-Package Microsoft.CodeAnalysis.Scripting

Step 2: Basic Execution of Dynamic Code

A simple way to execute dynamic C# code using Roslyn:

using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

class Program
{
    static async Task Main()
    {
        string code = "1 + 2";
        var result = await CSharpScript.EvaluateAsync<int>(code);
        Console.WriteLine("Result: " + result);
    }
}

Step 3: Providing Context for Execution

To allow dynamic scripts to use variables and functions from your main program, use a custom script state:

class ScriptGlobals
{
    public int X { get; set; } = 10;
}

var options = ScriptOptions.Default.AddReferences(typeof(ScriptGlobals).Assembly);
string code = "X * 2";
var result = await CSharpScript.EvaluateAsync<int>(code, options, new ScriptGlobals());
Console.WriteLine(result); // Output: 20

Step 4: Handling Exceptions in Dynamic Code

Since executing untrusted code can lead to runtime errors, wrap execution in try-catch:

try
{
    string invalidCode = "int x = 1 / 0;";
    await CSharpScript.EvaluateAsync(invalidCode);
}
catch (CompilationErrorException ex)
{
    Console.WriteLine("Compilation Error: " + string.Join("\n", ex.Diagnostics));
}
catch (Exception ex)
{
    Console.WriteLine("Runtime Error: " + ex.Message);
}

Step 5: Security Considerations

Executing user-provided code can be risky. Follow these best practices:

1. Use a Restricted Execution Context

Limit the namespaces and APIs available to the script:

var options = ScriptOptions.Default
    .AddReferences(typeof(object).Assembly) // Only essential assemblies
    .WithImports("System"); // Restrict available namespaces

2. Limit Execution Time

Run code in a separate thread with a timeout:

using System.Threading;
using System.Threading.Tasks;

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
    var task = CSharpScript.EvaluateAsync("while(true) {}", cancellationToken: cts.Token);
    await task;
}
catch (OperationCanceledException)
{
    Console.WriteLine("Execution Timed Out");
}

3. Use AppDomain Sandboxing (For Older .NET Versions)

In older .NET Framework applications, AppDomains can be used to isolate script execution. However, .NET Core and later versions no longer support AppDomains.

Step 6: Running More Complex Scripts with State

For multi-line scripts, use RunAsync instead of EvaluateAsync:

string script = @"
int Multiply(int a, int b) => a * b;
return Multiply(3, 4);
";
var result = await CSharpScript.RunAsync(script);
Console.WriteLine(result.ReturnValue); // Output: 12

Conclusion

Roslyn provides a powerful way to execute C# code dynamically while maintaining security and control. By following best practices such as limiting execution scope, handling errors, and enforcing timeouts, you can safely integrate dynamic scripting into your applications without exposing them to excessive risk.

0
875

Related

Storing passwords as plain text is dangerous. Instead, you should hash them using a strong, slow hashing algorithm like BCrypt, which includes built-in salting and resistance to brute-force attacks.

Step 1: Install BCrypt NuGet Package

Before using BCrypt, install the BCrypt.Net-Next package:

dotnet add package BCrypt.Net-Next

or via NuGet Package Manager:

Install-Package BCrypt.Net-Next

Step 2: Hash a Password

Use BCrypt.HashPassword() to securely hash a password before storing it:

using BCrypt.Net;

string password = "mySecurePassword123";
string hashedPassword = BCrypt.HashPassword(password);

Console.WriteLine(hashedPassword); // Output: $2a$12$...

Step 3: Verify a Password

To check a user's login attempt, use BCrypt.Verify():

bool isMatch = BCrypt.Verify("mySecurePassword123", hashedPassword);
Console.WriteLine(isMatch); // Output: True

Ensuring proper hashing should be at the top of your list when it comes to building authentication systems.

2
258

String interpolation, introduced in C# 6.0, provides a more readable and concise way to format strings compared to traditional concatenation (+) or string.Format(). Instead of manually inserting variables or placeholders, you can use the $ symbol before a string to directly embed expressions inside brackets.

string name = "Walt";
string job = 'Software Engineer';

string message = $"Hello, my name is {name} and I am a {job}";
Console.WriteLine(message);

This would produce the final output of:

Hello, my name is Walt and I am a Software Engineer

String interpolation can also be chained together into a multiline string (@) for even cleaner more concise results:

string name = "Walt";
string html = $@"
    <div>
        <h1>Welcome, {name}!</h1>
    </div>";
36
146

Slow initial load times can drive users away from your React application. One powerful technique to improve performance is lazy loading - loading components only when they're needed.

Let's explore how to implement this in React.

The Problem with Eager Loading

By default, React bundles all your components together, forcing users to download everything upfront. This makes navigation much quicker and more streamlined once this initial download is complete.

However, depending on the size of your application, it could also create a long initial load time.

import HeavyComponent from './HeavyComponent';
import AnotherHeavyComponent from './AnotherHeavyComponent';

function App() {
  return (
    <div>
      {/* These components load even if user never sees them */}
      <HeavyComponent />
      <AnotherHeavyComponent />
    </div>
  );
}

React.lazy() to the Rescue

React.lazy() lets you defer loading components until they're actually needed:

import React, { lazy, Suspense } from 'react';

// Components are now loaded only when rendered
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const AnotherHeavyComponent = lazy(() => import('./AnotherHeavyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
        <AnotherHeavyComponent />
      </Suspense>
    </div>
  );
}

Route-Based Lazy Loading

Combine with React Router for even better performance:

import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Implement these techniques in your React application today and watch your load times improve dramatically!

0
103