I am building a scripting engine in C# using Roslyn, and I would like to compile a piece of code from the user. In the scripting UI, the user can add references to other C# dlls that I don't know about.
In the user's code, I would like to find the symbols that are resolved looking into the known references, and the symbols that are not resolved.
For instance, I have a a dll that contains this class:
public class A {
public static double Stuff { get; }
}
And the users adds this dll as a reference for his script.
Then in his script, the user writes:
var x = A.Stuff * MyVariable;
return x;
I want to use Roslyn to compile this, and tell me that x and A.Stuff are known symbols and that MyVariable is not, so that I can infer from the code that MyVariable is a user input.
Right now I am doing this:
var syntaxTree = CSharpSyntaxTree.ParseText(usercode,
new CSharpParseOptions(LanguageVersion.Default, DocumentationMode.None, SourceCodeKind.Script));
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var userlib = MetadataReference.CreateFromFile(userlibPath);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { syntaxTree }, references: new[] { mscorlib, userlib });
var model = compilation.GetSemanticModel(syntaxTree);
But I don't know how to use the information from the semantic model. This is not very well documented anywhere...
You can try get variable declaration and check it:
var decl = model.GetSymbolInfo(identifier)
.Symbol
?.DeclaringSyntaxReferences
.FirstOrDefault();
Related
Tools like dotnet-script and CSI allow users to write, compile, and run C# like "scripts" rather than including their code in a complete pre-compiled project. These tools work great for command-line usage, but don't offer much in terms of integrating dynamic C# "scripts" into a larger C# application.
If I have an existing C# application which wishes to load additional classes into its existing namespaces via .csx "scripts", how do I do that? Is it possible?
I guess you need to compile and execute your C# script.
In my experience, I used C# scripts by referencing Microsoft.CodeAnalysis.CSharp.Scripting (version 3.*) directly.
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.*" />
Compilation
I suggest to use default compilation options:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Scripting;
// ...
ScriptOptions options = ScriptOptions.Default;
Maybe in the future, you'll need to add referenced assemblies to your script compilation.
So you need to compile your script (contained in a string variable in the code below):
byte[] assemblyBinaryContent;
var roslynScript = CSharpScript.Create(script, options);
var compilation = roslynScript.GetCompilation();
compilation = compilation.WithOptions(compilation.Options
.WithOptimizationLevel(OptimizationLevel.Release)
.WithOutputKind(OutputKind.DynamicallyLinkedLibrary));
using (var assemblyStream = new MemoryStream())
{
var result = compilation.Emit(assemblyStream);
if (!result.Success)
{
var errors = string.Join(Environment.NewLine, result.Diagnostics.Select(x => x));
throw new Exception("Compilation errors: " + Environment.NewLine + errors);
}
assemblyBinaryContent = assemblyStream.ToArray();
}
GC.Collect(); // it allows to force clear compilation stuff.
Assembly assembly = Assembly.Load(assemblyBinaryContent);
var ret = Run(assembly); // see next paragraph
Execution
Obviously you need an entry point to execute your script.
I found out this trickly solution. It works.
private object Run(Assembly assembly)
{
//Execute the script
var type = assembly.GetType("Submission#0");
var method = type.GetMethod("<Factory>", BindingFlags.Static | BindingFlags.Public);
var retTask = method.Invoke(null, new object[] { new object[2] }) as Task<object>;
return retTask.GetAwaiter().GetResult();
}
I hope it can help.
I wonder to know if it's possible. For example bootstrap has new version and i need to change cdn url and re-build project for RegisterBundles in asp.net web forms. It's simple url that we don't need to re-build project for that issue.
Is it possible that reading a *.txt file and using as c# code in a class. Class will be same as before and we will survive.
Example RegisterBundles code:
public class stylescriptbundle
{
public void RegisterBundles(BundleCollection bundles)
{
BundleTable.Bundles.Add(new ScriptBundle("/mybundle").Include(
"https://code.jquery.com/jquery-3.4.1.slim.min.js",
"https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js",
"https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
);
BundleTable.Bundles.UseCdn = true;
}
}
First, similar to what robbpriestley said, what you should do is read the strings from a file so you can include them in the bundle. Configuration file is best, but if you're hellbent on a .txt, try below:
var scripts = File.ReadAllLines("someConfigurationFile.txt");
var bundle = new ScriptBundle("/myBundle");
foreach(var script in scripts)
{
bundle.Include(script);
}
BundleTable.Bundles.UseCdn = true;
BundleTable.Bundles.Add(bundle);
(the above is just off the top of my head, you may have to tweak it to get it to work.)
That said, if you still want to compile code at run time, you can use the Microsoft.CSharp and Microsoft.CodeDom.Compiler namespaces, according to this tutorial. I'll try and summarize it here, for archival reasons.
Get your code into a string: var codeStr = File.ReadAllText("runtime compiled.cs");
Create a provider and compiler: var provider = new CSharpCodeProvider(); var parameters = new CodeParameters();
Define parameters of the compiler. It sounds like you'll definitely want to compile it into memory, and you'll need to reference any assemblies you use in that code: parameters.GenerateInMemory = true; parameters.ReferencedAssemblies.Add("necessaryAssembly.dll");
compile: var results = provider.CompileAssemblyFromSource(parameters, code);
Check errors
Get your assembly: var assembly = results.CompiledAssembly; so you can get your type: var program = assembly.GetType("first.program"); so you can get your method: var main = program.GetMethod("Main");
And then you can call your method with Invoke: main.Invoke(null, null); (check out reference on MethodInfo.)
Don't use a TXT file for this. Put the strings as settings into your web.config and then use the provided ASP.NET ConfigurationManager to reference them.
For example, see: https://stackoverflow.com/a/12892083/1348592
I am looking for an example in how to compile a project using Roslyn. The code below is an example I found in https://github.com/dotnet/roslyn/wiki/FAQ … This examples doesn't cover embedded files. Is that possible?
public class MyTask : Task {
public override bool Execute() {
var projectFileName = this.BuildEngine.ProjectFileOfTaskNode;
var project = ProjectCollection.GlobalProjectCollection.
GetLoadedProjects(projectFileName).Single();
var compilation = CSharpCompilation.Create(
project.GetPropertyValue("AssemblyName"),
syntaxTrees: project.GetItems("Compile").Select(
c => SyntaxFactory.ParseCompilationUnit(
c.EvaluatedInclude).SyntaxTree),
references: project.GetItems("Reference")
.Select(
r => new MetadataFileReference
(r.EvaluatedInclude)));
// Now work with compilation ...
}
}
Yes, it is possible to embed resources into result assembly using Roslyn.
To produce a result assembly CSharpCompilation type has an Emit method. This method has many parameters. One of them is manifestResources, which is responsible for adding embedded resources. You mas specify as many resources as you want. The following code demonstrates how you can use this parameter to emit an assembly with an embedded resource into peStream. It creates a resource with name "resourceName" and content that located at "path-to-resource" path.
void ProduceAssembly(CSharpCompilation compilation, Stream peStream)
{
ResourceDescription[] resources =
{
new ResourceDescription(
"resourceName",
() => File.OpenRead("path-to-resource"),
isPublic: true
)
};
var result = compilation.Emit(peStream, manifestResources: resources);
if (!result.Success)
{
var diagnostics = string.Join(Environment.NewLine, result.Diagnostics);
throw new Exception($"Compilation failed with: {diagnostics}");
}
}
Don't forget to check EmitResult.Success property to ensure that compilation completed successfully. Also ensure that peStream is disposed properly after compilation.
I am trying to use Roslyn API (Microsoft.CodeAnalysis) for loading an assembly and looking up the custom attributes. Code I have so far is:
var assembly = MetadataReference.CreateFromFile(tempFileName);
var compilation = CSharpCompilation.Create(null).AddReferences(assembly);
var assemblySymbol = (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(assembly);
var globalNamespace = assemblySymbol.GlobalNamespace.GetAttributes();
But GetAttributes() is empty.
Can Roslyn even do this? Am I going about it the right way?
I am trying to return a CLR object from Iron Ruby.
I have the following CLR type defined in C#
public class BuildMetaData
{
public string Description { get; set; }
}
I have the following IronRuby file:
$:.unshift(File.dirname(__FILE__) + '/../bin/Debug')
require 'mscorlib'
require 'Horn.Core.DSL.Domain'
class MetaDataFactory
def return_meta_data()
meta = Horn::Core::DSL::Domain::BuildMetaData.new
meta.Description = "A description of sorts"
meta
end
end
I have the following test that is failing:
[Fact]
public void Then_a_build_metadata_object_is_returned()
{
var engine = Ruby.CreateEngine();
engine.ExecuteFile("test.rb");
var code = String.Format("{0}.new.method :{1}", "MetaDataFactory", "return_meta_data");
var action = engine.CreateScriptSourceFromString(code).Execute();
var result = (BuildMetaData)engine.Operations.Call(action);
Assert.Equal(result.Description, "A description of sorts");
}
It fails when trying to cast the object returned from IronRuby.
I get the following error message:
[A]Horn.Core.DSL.Domain.BuildMetaData cannot be cast to [B]Horn.Core.DSL.Domain.BuildMetaData. Type A originates from 'Horn.Core.DSL.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'C:\Projects\horn\branches\rubydsl\src\Horn.Dsl.Specificatioin\bin\Debug\Horn.Core.DSL.Domain.dll'. Type B originates from 'Horn.Core.DSL.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\paul.cowan\AppData\Local\Temp\1vt2usw2.rxf\Horn.Dsl.Specificatioin\assembly\dl3\1d5ed945\7c19e429_1a97c901\Horn.Core.DSL.Domain.DLL'.
Is it possible to return CLR types from Iron Ruby
Rather than getting the method with a specially crafted string of Ruby, and then using C# to call the method, the preferred way to call into Ruby code from C# is the following:
var engine = IronRuby.Ruby.CreateEngine()
engine.ExecuteFile("test.rb")
var klass = engine.Runtime.Globals.GetVariable("MetaDataFactory")
var instance = engine.Operations.CreateInstance(klass)
engine.Operations.InvokeMember(instance, "return_meta_data")
~Jimmy
Actually it was all down to shadow copying.
My code ended up looking like this:
[Fact]
public void Then_the_object_should_be_accessible_in_csharp()
{
var engine = Ruby.CreateEngine();
engine.Runtime.LoadAssembly(typeof (BuildMetaData).Assembly);
engine.ExecuteFile(buildFile);
var klass = engine.Runtime.Globals.GetVariable("MetaDataFactory");
var instance = (RubyObject)engine.Operations.CreateInstance(klass);
//You must have shadow-copying turned off for the next line to run and for the test to pass.
//E.g. in R# "ReSharper/Options/Unit Testing/Shadow-Copy Assemblies being tested" should be un-checked.
var metaData = (BuildMetaData)engine.Operations.InvokeMember(instance, "return_meta_data");
Assert.Equal(metaData.Description, "A description of sorts");
Assert.Equal(metaData.Dependencies.Count, 1);
}
But if I turned off shadow-copying from the R# test runner then the test now passes.