OK, first first: there is no real way to use CSharpCodeProvider to dynamically compile a C # source completely in memory. There are methods that seem to support this functionality, but since the C # compiler is a natural executable file that cannot be started in the process, the original string is stored in a temporary file, the compiler is called in this file, and then the resulting assembly is saved to disk and then loaded for you using Assembly.Load.
Secondly, as you have discovered, you should be able to use the Compile method from AppDomain to load the assembly and grant it the necessary permissions. I came across the same unusual behavior, and after a lot of digging I discovered that it was a mistake in the framework. I sent a problem report to MS Connect .
Since the structure is already written to the file system anyway, the workaround is for the assembly to be written to a temporary file and then loaded as needed. However, at boot you need to temporarily approve permissions in AppDomain, since you have denied access to the file system. Here is an example of this snippet:
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert(); var assembly = Assembly.LoadFile(assemblyPath); CodeAccessPermission.RevertAssert();
From there, you can use assembly and reflection to call your method. Please note that this method allows you to raise the compilation process outside the isolated AppDomain, which is a plus in my opinion.
For reference, here is my Sandbox class, designed to make it easy to run script builds in a clean, separate AppDomain that has limited permissions and can be easily unloaded if necessary:
class Sandbox : MarshalByRefObject { const string BaseDirectory = "Untrusted"; const string DomainName = "Sandbox"; public Sandbox() { } public static Sandbox Create() { var setup = new AppDomainSetup() { ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory), ApplicationName = DomainName, DisallowBindingRedirects = true, DisallowCodeDownload = true, DisallowPublisherPolicy = true }; var permissions = new PermissionSet(PermissionState.None); permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess)); permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions, typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>()); return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap(); } public string Execute(string assemblyPath, string scriptType, string method, params object[] parameters) { new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert(); var assembly = Assembly.LoadFile(assemblyPath); CodeAccessPermission.RevertAssert(); Type type = assembly.GetType(scriptType); if (type == null) return null; var instance = Activator.CreateInstance(type); return string.Format("{0}", type.GetMethod(method).Invoke(instance, parameters)); } }
Quick note: if you use this method to provide security evidence for the new AppDomain, you need to sign your assembly to give it a strong name.
Note that this works great at startup, but if you really need a bulletproof script environment, you need to take one more step and isolate the script in a separate process to make sure that scripts that do malicious (or just plain dumb) things like stack overflows, fork bombs and memory situations do not ruin the entire application process. I can give you more information about this if you need it.