using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using netDxf;
namespace DxfToCSharp.Compilation;
public class CompilationService
{
public class CompilationResult
{
public bool Success { get; }
public string? AssemblyPath { get; }
public string Output { get; }
public Assembly? Assembly { get; }
public CompilationResult(bool success, string? assemblyPath, string output, Assembly? assembly = null)
{
Success = success;
AssemblyPath = assemblyPath;
Output = output;
Assembly = assembly;
}
}
///
/// Compiles C# source code to a DLL file on disk
///
public CompilationResult CompileToFile(string sourceCode)
{
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var compilationOptions = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release);
var references = GetMetadataReferences();
var assemblyName = "GeneratedDxf_" + Guid.NewGuid().ToString("N");
// Secure path construction to prevent path traversal attacks
var baseTempPath = Path.GetTempPath();
var tempDir = Path.Combine(baseTempPath, "DxfToCSharp");
// Validate that the resulting path is still within the temp directory
var normalizedTempDir = Path.GetFullPath(tempDir);
var normalizedBasePath = Path.GetFullPath(baseTempPath);
if (!normalizedTempDir.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Invalid path detected - potential path traversal attempt");
}
Directory.CreateDirectory(tempDir);
// Validate assembly name contains only safe characters
if (assemblyName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
{
throw new ArgumentException("Assembly name contains invalid characters");
}
var dllPath = Path.Combine(tempDir, assemblyName + ".dll");
var pdbPath = Path.Combine(tempDir, assemblyName + ".pdb");
// Validate that the resulting file paths are still within the temp directory
var normalizedDllPath = Path.GetFullPath(dllPath);
var normalizedPdbPath = Path.GetFullPath(pdbPath);
if (!normalizedDllPath.StartsWith(normalizedTempDir, StringComparison.OrdinalIgnoreCase) ||
!normalizedPdbPath.StartsWith(normalizedTempDir, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Invalid file path detected - potential path traversal attempt");
}
var compilation = CSharpCompilation.Create(
assemblyName,
new[] { syntaxTree },
references,
compilationOptions);
using var dllStream = new FileStream(dllPath, FileMode.Create, FileAccess.Write);
using var pdbStream = new FileStream(pdbPath, FileMode.Create, FileAccess.Write);
var emitResult = compilation.Emit(dllStream, pdbStream);
if (!emitResult.Success)
{
var diagnostics = string.Join(Environment.NewLine,
emitResult.Diagnostics
.Where(d => d.Severity == DiagnosticSeverity.Error)
.Select(d => d.ToString()));
return new CompilationResult(false, null, diagnostics);
}
return new CompilationResult(true, dllPath, "Compilation succeeded.");
}
///
/// Compiles C# source code to memory and returns the loaded assembly
///
public CompilationResult CompileToMemory(string sourceCode)
{
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var compilation = CSharpCompilation.Create(
"DynamicAssembly",
new[] { syntaxTree },
GetMetadataReferences(),
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using var ms = new MemoryStream();
var result = compilation.Emit(ms);
if (!result.Success)
{
var errors = string.Join("\n", result.Diagnostics
.Where(d => d.Severity == DiagnosticSeverity.Error)
.Select(d => d.ToString()));
return new CompilationResult(false, null, $"Compilation failed:\n{errors}\n\nGenerated code:\n{sourceCode}");
}
ms.Seek(0, SeekOrigin.Begin);
var assembly = Assembly.Load(ms.ToArray());
return new CompilationResult(true, null, "Compilation succeeded.", assembly);
}
///
/// Executes the Create method from a compiled assembly and returns a DxfDocument
///
public DxfDocument? ExecuteCreateMethod(Assembly assembly)
{
// Find the first public static class with a static Create method that returns DxfDocument
var type = assembly.GetTypes()
.FirstOrDefault(t => t is { IsClass: true, IsSealed: true, IsAbstract: true } && // static class
t.GetMethod("Create", BindingFlags.Public | BindingFlags.Static) != null);
if (type == null)
throw new InvalidOperationException("No static class with a 'Create' method found in compiled assembly.");
var method = type.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
if (method == null)
throw new InvalidOperationException($"Static method 'Create' not found on type '{type.Name}'.");
var result = method.Invoke(null, null) as DxfDocument;
return result;
}
///
/// Executes the Create method from a compiled assembly file and returns a DxfDocument
///
public DxfDocument? ExecuteCreateMethod(string assemblyPath)
{
var assembly = Assembly.LoadFile(assemblyPath);
return ExecuteCreateMethod(assembly);
}
///
/// Runs the Create method from an assembly file and saves the result to a DXF file
///
public string RunCreateMethod(string assemblyPath)
{
try
{
var dxf = ExecuteCreateMethod(assemblyPath);
if (dxf != null)
{
// Secure path construction to prevent path traversal attacks
var baseTempPath = Path.GetTempPath();
var fileName = "Generated_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".dxf";
// Validate filename contains only safe characters
if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
{
throw new ArgumentException("Generated filename contains invalid characters");
}
var tempDir = Path.Combine(baseTempPath, "DxfToCSharp");
var tempFile = Path.Combine(tempDir, fileName);
// Validate that the resulting paths are still within the temp directory
var normalizedTempDir = Path.GetFullPath(tempDir);
var normalizedBasePath = Path.GetFullPath(baseTempPath);
var normalizedTempFile = Path.GetFullPath(tempFile);
if (!normalizedTempDir.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase) ||
!normalizedTempFile.StartsWith(normalizedTempDir, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Invalid path detected - potential path traversal attempt");
}
Directory.CreateDirectory(Path.GetDirectoryName(tempFile)!);
dxf.Save(tempFile);
return "Executed successfully. Saved DXF to: " + tempFile;
}
return "Create method returned null.";
}
catch (TargetInvocationException tie)
{
return "Runtime error: " + tie.InnerException;
}
catch (TargetParameterCountException ex)
{
return "Method parameter error: " + ex.Message;
}
catch (ArgumentException ex)
{
return "Invalid argument: " + ex.Message;
}
catch (InvalidOperationException ex)
{
return "Invalid operation: " + ex.Message;
}
catch (UnauthorizedAccessException ex)
{
return "Access denied: " + ex.Message;
}
catch (IOException ex)
{
return "I/O error: " + ex.Message;
}
catch (Exception ex)
{
return "Unexpected error: " + ex.Message;
}
}
///
/// Gets the necessary references for compilation, including System.Drawing.Primitives
///
private static MetadataReference[] GetMetadataReferences()
{
var references = new List();
// Helper method to safely add metadata reference
void TryAddReference(Assembly assembly)
{
if (assembly != null && !string.IsNullOrEmpty(assembly.Location) && File.Exists(assembly.Location))
{
references.Add(MetadataReference.CreateFromFile(assembly.Location));
}
}
// Core system assemblies
TryAddReference(typeof(object).Assembly);
TryAddReference(typeof(Console).Assembly);
TryAddReference(typeof(List<>).Assembly);
TryAddReference(typeof(Enumerable).Assembly);
TryAddReference(typeof(Uri).Assembly);
// netDxf
TryAddReference(typeof(DxfDocument).Assembly);
// Add System.Runtime and System.Collections
try
{
TryAddReference(Assembly.Load("System.Runtime"));
TryAddReference(Assembly.Load("System.Collections"));
}
catch (FileNotFoundException)
{
// Ignore if assembly not available
}
catch (FileLoadException)
{
// Ignore if assembly cannot be loaded
}
catch (BadImageFormatException)
{
// Ignore if assembly format is invalid
}
// Add System.Drawing.Primitives reference for Color type
try
{
TryAddReference(Assembly.Load("System.Drawing.Primitives"));
}
catch (FileNotFoundException)
{
// Try alternative approach for System.Drawing.Primitives
try
{
var drawingAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "System.Drawing.Primitives");
if (drawingAssembly != null)
{
TryAddReference(drawingAssembly);
}
}
catch (FileNotFoundException)
{
// Ignore if System.Drawing.Primitives is not available
}
catch (FileLoadException)
{
// Ignore if System.Drawing.Primitives cannot be loaded
}
}
catch (FileLoadException)
{
// Ignore if System.Drawing.Primitives cannot be loaded
}
catch (BadImageFormatException)
{
// Ignore if System.Drawing.Primitives format is invalid
}
// Add reference to netstandard if available
try
{
TryAddReference(Assembly.Load("netstandard"));
}
catch (FileNotFoundException)
{
// Ignore if netstandard is not available
}
catch (FileLoadException)
{
// Ignore if netstandard cannot be loaded
}
catch (BadImageFormatException)
{
// Ignore if netstandard format is invalid
}
// Add entry assembly if available (for Avalonia app)
try
{
var appAsm = Assembly.GetEntryAssembly();
if (appAsm != null)
{
TryAddReference(appAsm);
}
}
catch (FileNotFoundException)
{
// Ignore if entry assembly not available
}
catch (FileLoadException)
{
// Ignore if entry assembly cannot be loaded
}
catch (BadImageFormatException)
{
// Ignore if entry assembly format is invalid
}
// Add any other relevant loaded assemblies
foreach (var loaded in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
if (!string.IsNullOrEmpty(loaded.Location) && File.Exists(loaded.Location))
{
// Avoid duplicates
if (!references.Any(r => r is PortableExecutableReference per && per.FilePath == loaded.Location))
{
// Include specific assemblies that might be useful
var name = loaded.GetName().Name;
if (name is "System.Private.CoreLib" or "System.Runtime" or "netstandard")
{
TryAddReference(loaded);
}
}
}
}
catch (NotSupportedException)
{
// Some dynamic assemblies don't have a location
}
catch (FileNotFoundException)
{
// Assembly file not found
}
catch (InvalidOperationException)
{
// Assembly operation not supported
}
}
return references.ToArray();
}
}