Compiling/Serializing a piece of code in c# - c#

my goal is to write a class that can compile/serialize any piece of code. I am thinking of some ideas and it's just a concept, I don't expect you to write entire solution, I am thinking of best way to do that.
I thought about two ideas:
Create an interface in compiler class, then read interface method body and compile it into separate assembly using roslyn.
Serialize expression tree.
AD 1. First concept
public class Runnable : IRunnable
{
void Run();
}
public interface ICodeRunner
{
void RunCode<T>() where T : IRunnable
}
public class CodeRunner : ICodeRunner
{
public void RunCode<T>() where T : IRunnable
{
MethodInfo mi = typeof(T).GetMethod("Run");
MethodBody mb = mi.GetMethodBody();
// somehow get method body if possible and compile it using roslyn.
// like this:
// string syntaxTree = mb.GetMethodBodyToString() // this method doesn't exist. There must be a way to do that
// CSharpCompilation compilation = CSharpCompilation.Create(
// "abc",
// new[] { syntaxTree },
// new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) },
// new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// using (var dllStream = new FileStream(#"abc.dll", FileMode.OpenOrCreate))
// using (var pdbStream = new FileStream(#"abc.pdb", FileMode.OpenOrCreate))
// {
// var emitResult = compilation.Emit(dllStream, pdbStream);
// if (!emitResult.Success)
// {
// // emitResult.Diagnostics
// }
// }
}
}
The alternative is to serialize lambda as expression tree into a file, I saw some libraries to do that.
Are there any easier ways to this?
My general use case is to create a class that can serialize some code (logic) and payload into two separate files, send it over the wire and run piece of code + payload on a different machine. The interface is exe file and input file which is sent over the wire. Are there any ready solution for this problem?

Related

How can I make the CompletionService aware of other documents in the project?

I'm building an application that allows users to define, edit and execute C# scripts.
The definition consists of a method name, an array of parameter names and the method's inner code, e.g:
Name: Script1
Parameter Names: arg1, arg2
Code: return $"Arg1: {arg1}, Arg2: {arg2}";
Based on this definition the following code can be generated:
public static object Script1(object arg1, object arg2)
{
return $"Arg1: {arg1}, Arg2: {arg2}";
}
I've successfully set up an AdhocWorkspace and a Project like this:
private readonly CSharpCompilationOptions _options = new CSharpCompilationOptions(OutputKind.ConsoleApplication,
moduleName: "MyModule",
mainTypeName: "MyMainType",
scriptClassName: "MyScriptClass"
)
.WithUsings("System");
private readonly MetadataReference[] _references = {
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
};
private void InitializeWorkspaceAndProject(out AdhocWorkspace ws, out ProjectId projectId)
{
var assemblies = new[]
{
Assembly.Load("Microsoft.CodeAnalysis"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp"),
Assembly.Load("Microsoft.CodeAnalysis.Features"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp.Features")
};
var partTypes = MefHostServices.DefaultAssemblies.Concat(assemblies)
.Distinct()
.SelectMany(x => x.GetTypes())
.ToArray();
var compositionContext = new ContainerConfiguration()
.WithParts(partTypes)
.CreateContainer();
var host = MefHostServices.Create(compositionContext);
ws = new AdhocWorkspace(host);
var projectInfo = ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Create(),
"MyProject",
"MyProject",
LanguageNames.CSharp,
compilationOptions: _options, parseOptions: new CSharpParseOptions(LanguageVersion.CSharp7_3, DocumentationMode.None, SourceCodeKind.Script)).
WithMetadataReferences(_references);
projectId = ws.AddProject(projectInfo).Id;
}
And I can create documents like this:
var document = _workspace.AddDocument(_projectId, "MyFile.cs", SourceText.From(code)).WithSourceCodeKind(SourceCodeKind.Script);
For each script the user defines, I'm currently creating a separate Document.
Executing the code works as well, using the following methods:
First, to compile all documents:
public async Task<Compilation> GetCompilations(params Document[] documents)
{
var treeTasks = documents.Select(async (d) => await d.GetSyntaxTreeAsync());
var trees = await Task.WhenAll(treeTasks);
return CSharpCompilation.Create("MyAssembly", trees, _references, _options);
}
Then, to create an assembly out of the compilation:
public Assembly GetAssembly(Compilation compilation)
{
try
{
using (MemoryStream ms = new MemoryStream())
{
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
foreach (Diagnostic diagnostic in emitResult.Diagnostics)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
var buffer = ms.GetBuffer();
var assembly = Assembly.Load(buffer);
return assembly;
}
return null;
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
And, finally, to execute the script:
public async Task<object> Execute(string method, object[] params)
{
var compilation = await GetCompilations(_documents);
var a = GetAssembly(compilation);
try
{
Type t = a.GetTypes().First();
var res = t.GetMethod(method)?.Invoke(null, params);
return res;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
So far, so good. This allows users to define scripts that can all each other.
For editing I would like to offer code completion and am currently doing this:
public async Task<CompletionList> GetCompletionList(Document doc, string code, int offset)
{
var newDoc = doc.WithText(SourceText.From(code));
_workspace.TryApplyChanges(newDoc.Project.Solution);
var completionService = CompletionService.GetService(newDoc);
return await completionService.GetCompletionsAsync(newDoc, offset);
}
NOTE: The code snippet above was updated to fix the error Jason mentioned in his answer regarding the use of doc and document. That was, indeed, due to the fact that the code shown here was extracted (and thereby modified) from my actual application code. You can find the original erroneous snippet I posted in his answer and, also, further below a new version which addresses the actual issue that was causing my problems.
The problem now is that GetCompletionsAsync is only aware of definitions within the same Document and the references used when creating the workspace and project, but it apparently does not have any reference to the other documents within the same project. So the CompletionList does not contain symbols for the other user scripts.
This seems strange, because in a "live" Visual Studio project, of course, all files within a project are aware of each other.
What am I missing? Are the project and/or workspace set up incorrectly? Is there another way of calling the CompletionService? Are the generated document codes missing something, like a common namespace?
My last resort would be to merge all methods generated from users' script definitions into one file - is there another way?
FYI, here are a few useful links that helped me get this far:
https://www.strathweb.com/2018/12/using-roslyn-c-completion-service-programmatically/
Roslyn throws The language 'C#' is not supported
Roslyn service is null
Updating AdHocWorkspace is slow
Roslyn: is it possible to pass variables to documents (with SourceCodeKind.Script)
UPDATE 1:
Thanks to Jason's answer I've updated the GetCompletionList method as follows:
public async Task<CompletionList> GetCompletionList(Document doc, string code, int offset)
{
var docId = doc.Id;
var newDoc = doc.WithText(SourceText.From(code));
_workspace.TryApplyChanges(newDoc.Project.Solution);
var currentDoc = _workspace.CurrentSolution.GetDocument(docId);
var completionService = CompletionService.GetService(currentDoc);
return await completionService.GetCompletionsAsync(currentDoc, offset);
}
As Jason pointed out, the cardinal error was not fully taking the immutability of the project and it's documents into account. The Document instance I need for calling CompletionService.GetService(doc) must be the actual instance contained in the current solution - and not the instance created by doc.WithText(...), because that instance has no knowledge of anything.
By storing the DocumentId of the original instance and using it to retrieve the updated instance within the solution, currentDoc, after applying the changes, the completion service can (as in "live" solutions) reference the other documents.
UPDATE 2: In my original question the code snippets used SourceCodeKind.Regular, but - at least in this case - it must be SourceCodeKind.Script, because otherwise the compiler will complain that top-level static methods are not allowed (when using C# 7.3). I've now updated the post.
So one thing looks a bit fishy here:
public async Task<CompletionList> GetCompletionList(Document doc, string code, int offset)
{
var newDoc = document.WithText(SourceText.From(code));
_workspace.TryApplyChanges(newDoc.Project.Solution);
var completionService = CompletionService.GetService(newDoc);
return await completionService.GetCompletionsAsync(document, offset);
}
(Note: your parameter name is "doc" but you're using "document" so I'm guessing this code is something you pared down from the full example. But just wanted to call that out since you might have introduced errors while doing that.)
So main fishy bit: Roslyn Documents are snapshots; a document is a pointer within the entire snapshot of the entire solution. Your "newDoc" is a new document with the text that you've substituted, and you're updating thew workspace to contain that. You're however still handing in the original document to GetCompletionsAsync, which means you're still asking for the old document in that case, which might have stale code. Furthermore, because it's all a snapshot, the changes made to the main workspace by calling TryApplyChanges won't in any way be reflected in your new document objects. So what I'm guessing might be happening here is you're passing in a Document object that doesn't actually have all the text documents updated at once, but most of them are still empty or something similar.

Creating a class with a generic csv reader via a method

As a headsup, I'm new to stackoverflow so please do tell me if I'm just bad at searching for a solution.
I have a class library that's able to read csv files depending on the public members of a class and then assign them. However, I don't know a way to avoid a long switch or if else when I want to call/invoke the method.
Here's how I currently use it:
public class DataSetup
{
List<object> ObjList = new List<object>();
public DataSetup(string file)
{
switch (file)
{
case "Persons.csv":
AssignCsvData<Person>(file);
break;
case "Address.csv":
AssignCsvData<Address>(file);
break;
}
}
public void AssignCsvData<T>(string file) where T : CsvableBase, new()
{
var cr = new CsvReader<T>();
var csvObjs = cr.Read(file);
foreach (var obj in csvObjs)
{
ObjList.Add(obj);
}
}
}
The switch is not "done", since I'm hoping for a better way.
So is there a way to call the AssignCsvData without the switch?
So far I've tried taking a type as a parameter for the constructor, but I can't use it as a type when it's a variable.
If the name of the file always represents a class. In your case there is a mismatch between Persons.csv and Person, but say they do match then the following could work using reflection, rather than a switch statement:
var type = Type.GetType(Path.GetFileNameWithoutExtension(file));
var method = typeof(Processor)
.GetMethod("AssignCsvData")
.MakeGenericMethod(type);
method.Invoke(new Processor(), file);
NOTE: If you'd like to test this in LinqPad, don't forget to use the "UserQuery+" namespace to get the type. i.e.:
var type = Type.GetType($"UserQuery+{Path.GetFileNameWithoutExtension(file)}");

WF 4.5 Compile CSharpValue<T> error. What is the correct method?

I've been uniting testing two simple examples of compiling CSharpValue activities. One works and the other doesn't I can't figure out why. If someone could point out the issue and optionally a change to correct it if possible.
Details:
The first unit test works SequenceActivityCompile() the second CodeActivityCompile fails with a NotSupportedException (Expression Activity type CSharpValue requires compilation in order to run. Please ensure that the workflow has been compiled.)
I heard somewhere this can be related to ForImplementation but CodeActivityCompile has the same error whether its value is true or false.
This example is a basic adaption of the Microsoft example at: https://msdn.microsoft.com/en-us/library/jj591618(v=vs.110).aspx
This example blog post discussing compiling C# expressions in WF 4+ at length. If anyone reaching this question needs a basic introduction to the topic:
http://blogs.msdn.com/b/tilovell/archive/2012/05/25/wf4-5-using-csharpvalue-lt-t-gt-and-csharpreference-lt-t-gt-in-net-4-5-compiling-expressions-and-changes-in-visual-studio-generated-xaml.aspx
Related Code:
[TestMethod]
public void SequenceActivityCompile()
{
Activity sequence = new Sequence
{
Activities = { new CSharpValue<string>("\"Hello World \"") }
};
CompileExpressions(sequence);
var result = WorkflowInvoker.Invoke(sequence);
}
[TestMethod]
public void CodeActivityCompile()
{
var code = new CSharpValue<String>("\"Hello World\"");
CompileExpressions(code);
var result = WorkflowInvoker.Invoke(code);
}
void CompileExpressions(Activity activity)
{
// activityName is the Namespace.Type of the activity that contains the
// C# expressions.
string activityName = activity.GetType().ToString();
// Split activityName into Namespace and Type.Append _CompiledExpressionRoot to the type name
// to represent the new type that represents the compiled expressions.
// Take everything after the last . for the type name.
//string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
string activityType = "TestType";
// Take everything before the last . for the namespace.
//string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
string activityNamespace = "TestSpace";
// Create a TextExpressionCompilerSettings.
TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
{
Activity = activity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = false
};
// Compile the C# expression.
TextExpressionCompilerResults results =
new TextExpressionCompiler(settings).Compile();
// Any compilation errors are contained in the CompilerMessages.
if (results.HasErrors)
{
throw new Exception("Compilation failed.");
}
// Create an instance of the new compiled expression type.
ICompiledExpressionRoot compiledExpressionRoot =
Activator.CreateInstance(results.ResultType,
new object[] { activity }) as ICompiledExpressionRoot;
// Attach it to the activity.
System.Activities.Expressions.CompiledExpressionInvoker.SetCompiledExpressionRoot(
activity, compiledExpressionRoot);
}

How to use automatically generated proxy class?

I'd like to use a web service from a database to gather informations. Right now, I implemented to web service, turned it into a proxy class via wsdl.exe but I'm slightly irritated by the outcome. The normal way to call that class is new object -> method -> parameters ->happiness. This thing only consists of partial classes and wants strange parameters. I'm not even sure if I got the right method to get the wanted information.
This seems to be the needed method:
public UniProtId2DomainIdsRecordType[] UniProtId2DomainIds (UniProtId2DomainIdsRequestRecordType UniProtId2DomainIdsRequestRecord)
{
object[] results = this.Invoke("UniProtId2DomainIds", new object[] {
UniProtId2DomainIdsRequestRecord});
return ((UniProtId2DomainIdsRecordType[])(results[0]));
}
This seems to be one of the needed classes:
public partial class UniProtId2DomainIdsRequestRecordType
{
private string uniprot_accField;
/// <remarks/>
public string uniprot_acc
{
get
{
return this.uniprot_accField;
}
set
{
this.uniprot_accField = value;
}
}
}
(That's the whole class, generated by wsdl.exe -> https://www.dropbox.com/s/yg909ibdq02js5a/GetCath.cs)
But as soon as I try to use it as I think it should work... well... my experiments on this (none of them working):
UniProtId2DomainIdsRequestRecordType Uni2Cath = new UniProtId2DomainIdsRequestRecordType();
Uni2Cath.uniprot_acc = "P0A7N9";
UniProtId2DomainIdsRecordType[] UniProtId2DomainIds;
UniProtId2DomainIdsRecordType test = new UniProtId2DomainIdsRecordType();
test.uniprot_acc = "P0A7N9";
UniProtId2DomainIdsRecordType[] UniProtId2DomainIds(test);
All I need is to get a string like P0A7N9 to be passed to the server.
(The reference to this webservice: http://api.cathdb.info/api/soap/dataservices/wsdl#op.o159501052 )
Can someone give me a hint how to handle this, please?
The easiest way would be to add this web service as Service Reference to your project. Then you can call the different methods. Use this as the address: http://api.cathdb.info/api/soap/dataservices/wsdl
using (var ser = new DataServicesPortTypeClient())
{
var results = ser.UniProtId2DomainIds(new UniProtId2DomainIdsRequestRecordType
{
uniprot_acc = "P0A7N9"
});
if (results != null)
{
var geneName = results.gene_name;
var speciesName = results.species_name;
}
}
If you want to use your generated class do this:
using (var service = new DataServices())
{
var results = service.UniProtId2DomainIds(new UniProtId2DomainIdsRequestRecordType
{
uniprot_acc = "P0A7N9"
});
if (results != null && results.Length >0)
{
var geneName = results[0].gene_name;
var speciesName = results[0].species_name;
}
}
As John suggested in the comments, ASMX and wsdl.exe are deprecated technologies. You should be using Service References and svcutil.exe

How best to create and execute a method in a .NET (C#) class dynamically through configuration

I'm thinking of something along the lines of the "Inline Task" in MsBuild. For reference: http://msdn.microsoft.com/en-us/library/dd722601.aspx
I'd like to find or create a framework which allows me to override a method via configuration. For example if I have a well known base class which has a method Execute(args), how can I supply an overridden method implementation at deployment time, without requiring new code, build, release cycle? I would like to actually plug in the method body into a config file or preferably a database table.
I assume this would be done either with code dom, dynamic language integration, or perhaps something like powershell(?). I'm looking for recommendations or perhaps a library someone has already written.
The application is written in C#. Preferably the extension would also be in C#, but I'm open to other ideas as well.
Update: Technically I don't even have to actually override a method. It would be sufficient to just be able to dynamically execute some external source code, passing in an arg and returning a result.
Update. I ended up writing code to instantiate a PowerShell object and execute a script dynamically to return a value. Here is a snippet of code I used.
public static Collection<PSObject> ExecuteScript(string code, string variableName, object variableValue)
{
PowerShell ps = PowerShell.Create();
ps.AddScript(code);
if (!string.IsNullOrWhiteSpace(variableName))
{
ps.Runspace.SessionStateProxy.SetVariable(variableName, variableValue);
}
var result = ps.Invoke();
return result;
}
Then in the calling code, I simply check the first PSObject in the return value, and pull the resulting value from it. It works great. Thanks for all the responses.
Here are two examples of dynamic execution. I have used neither though so I can't comment further.
http://www.codeproject.com/KB/dotnet/evaluator.aspx
http://www.csharpfriends.com/articles/getarticle.aspx?articleid=118
Regarding namespaces, from the second article you can add assemblies through the CompilerParameter class.
// Create the C# compiler
CSharpCodeProvider csCompiler = new CSharpCodeProvider();
ICodeCompiler iCodeCompiler = csCompiler.CreateCompiler();
// input params for the compiler
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.OutputAssembly = "CSharpFriends.dll";
compilerParams.ReferencedAssemblies.Add("system.dll");
One option would be to use Iron Python (or another DLR language). Your Execute method would then lookup the script in your configuration file, compile it and execute it all at runtime.
Including the necessary Iron Python assemblies with your project isn't a significant overhead.
You might need to do some plumbing to expose other parts of your application to the python runtime environment but this is quite easy to do.
You can use interfaces and then resolve the concrete classes at runtime e.g. using configuration files.
Check the various Dependency Injection Containers at http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
Managed Extensibility Framework (MEF) might be suitable as well. It was included as part of .NET 4.
http://msdn.microsoft.com/en-us/library/dd460648.aspx
http://mef.codeplex.com/
If the extensibility is just for one method then MEF would be overkill. If what you are extending will grow over time then I think MEF would provide the most robust and long-term manageable framework.
It looks like you might want to have a look at the Factory Pattern; returning delegates. Unfortunately you will need a type to 'house' the method, so you would typically generate code like:
namespace Dynamic {
public static int Foo(int bar) {
// .. Configured body here.
}
}
It's important that your factory does not generate methods it has seen before. Here is an example:
static class Delegates
{
private static Func<Func<int, string>> _test;
public static Func<int, string> Test
{
get
{
return _test();
}
}
static Delegates()
{
// Use your config variables instead of the "return arg.ToString();"
CreateFactory<Func<int, string>>(x => _test = x, "return arg.ToString();");
}
private static void CreateFactory<TDelegate>(Action<Func<TDelegate>> locationSetter, string identifier)
{
locationSetter(() =>
{
var result = Generate<TDelegate>(identifier);
locationSetter(() => result);
return result;
});
}
private static string GenerateSignature<TDelegate>()
{
// Create the signature of the delegate.
var t = typeof(TDelegate);
if (!typeof(Delegate).IsAssignableFrom(t))
throw new Exception("TDelegate must be delegate type.");
var invoke = t.GetMethod("Invoke");
var sig = new StringBuilder();
// Append the return type.
if (invoke.ReturnType == typeof(void))
sig.Append("void");
else
sig.Append(invoke.ReturnType.FullName);
sig.Append(" ");
sig.Append("Invoke(");
// Append the parameters.
var param = invoke.GetParameters();
for (var i = 0; i < param.Length; i++)
{
if (i != 0)
sig.Append(", ");
sig.Append(param[i].ParameterType.FullName);
sig.Append(" ");
sig.Append(param[i].Name);
}
sig.Append(")");
return sig.ToString();
}
private static TDelegate Generate<TDelegate>(string code)
{
// Generate the containing class and method.
var codeBuilder = new StringBuilder(50);
codeBuilder.AppendLine("using System;");
codeBuilder.Append("namespace Dynamic { class DynamicClass { public static ");
codeBuilder.Append(GenerateSignature<TDelegate>());
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(code);
codeBuilder.AppendLine("} } }");
var compilerVersion = new Version(1, 0, 0, 0);
// Create the compiler parameters.
var parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
parameters.ReferencedAssemblies.Clear();
foreach (var referenceAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
parameters.ReferencedAssemblies.Add(referenceAssembly.Location);
// Figure out which version we are compiling against.
var an = new AssemblyName(referenceAssembly.FullName);
if (an.Name == "mscorlib" && compilerVersion < an.Version)
{
compilerVersion = an.Version;
}
}
var cp = new CSharpCodeProvider(
new Dictionary<string, string>() { { "CompilerVersion", string.Format("v{0}.{1}", compilerVersion.Major, compilerVersion.Minor) } }
);
var results = cp.CompileAssemblyFromSource(parameters, codeBuilder.ToString());
if (results.Errors.HasErrors)
throw new Exception("Method failed to compile.");
var assembly = results.CompiledAssembly;
if (assembly == null)
throw new Exception("Method failed to compile.");
var t = assembly.GetType("Dynamic.DynamicClass");
if (t == null)
throw new Exception("Method failed to compile.");
var m = t.GetMethod("Invoke");
if (m == null)
throw new Exception("Method failed to compile.");
return (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), m);
}
}

Categories