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.

7
1348

Related

When working with URLs in C#, encoding is essential to ensure that special characters (like spaces, ?, &, and =) don’t break the URL structure. The recommended way to encode a string for a URL is by using Uri.EscapeDataString(), which converts unsafe characters into their percent-encoded equivalents.

string rawText = "hello world!";
string encodedText = Uri.EscapeDataString(rawText);

Console.WriteLine(encodedText); // Output: hello%20world%21

This method encodes spaces as %20, making it ideal for query parameters.

For ASP.NET applications, you can also use HttpUtility.UrlEncode() (from System.Web), which encodes spaces as +:

using System.Web;

string encodedText = HttpUtility.UrlEncode("hello world!");
Console.WriteLine(encodedText); // Output: hello+world%21

For .NET Core and later, Uri.EscapeDataString() is the preferred choice.

34
1699

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>";
39
159

Working with dates is a common requirement in many applications, and calculating the difference between two dates is a particularly frequent task.

C# provides several powerful built-in methods to handle date arithmetic efficiently. Let's explore how to calculate date differences in C#.

Using DateTime and TimeSpan

The most straightforward way to calculate the difference between two dates in C# is by using the DateTime struct and the TimeSpan class:

DateTime startDate = new DateTime(2023, 1, 1);
DateTime endDate = new DateTime(2023, 12, 31);

TimeSpan difference = endDate - startDate;

Console.WriteLine($"Total days: {difference.TotalDays}");
Console.WriteLine($"Total hours: {difference.TotalHours}");
Console.WriteLine($"Total minutes: {difference.TotalMinutes}");
Console.WriteLine($"Total seconds: {difference.TotalSeconds}");

Getting Specific Units

Sometimes you need the difference in specific units (years, months, days). The TimeSpan class doesn't directly provide years and months, since these units vary in length. Here's how to handle this:

int years = endDate.Year - startDate.Year;
int months = endDate.Month - startDate.Month;

if (months < 0)
{
    years--;
    months += 12;
}

// Adjust for day differences
if (endDate.Day < startDate.Day)
{
    months--;
    int daysInMonth = DateTime.DaysInMonth(startDate.Year, startDate.Month);
    int dayDifference = daysInMonth - startDate.Day + endDate.Day;
    Console.WriteLine($"Years: {years}, Months: {months}, Days: {dayDifference}");
}
else
{
    int dayDifference = endDate.Day - startDate.Day;
    Console.WriteLine($"Years: {years}, Months: {months}, Days: {dayDifference}");
}

Using DateTimeOffset for Time Zone Awareness

If your application needs to handle dates across different time zones, consider using DateTimeOffset:

DateTimeOffset startDateOffset = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.FromHours(-5));
DateTimeOffset endDateOffset = new DateTimeOffset(2023, 12, 31, 0, 0, 0, TimeSpan.FromHours(1));

TimeSpan timeDifference = endDateOffset - startDateOffset;
Console.WriteLine($"Total days including time zone difference: {timeDifference.TotalDays}");

Practical Applications

Date difference calculations are useful in many scenarios:

  • Calculating age from birth date
  • Determining duration between events
  • Computing business days between dates
  • Scheduling recurring events

With these techniques, you can handle most date arithmetic requirements in your C# applications efficiently and accurately.

6
855