How can I look up custom attributes for a class - c#

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?

Related

What's the best way to load a CSX script from a C# application?

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.

Reading txt file and import as a c# code in a class

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

Fully Qualified Namespace Metadata Error with Roslyn

I am trying to make a code analyzer which checks for fully qualified using statements. This link has been incredibly helpful, and the basis for my solution (How can I get the fully qualified namespace from a using directive in Roslyn?) but I am running into a problem when I try to access the location of the symbol for the using directive. My code looks like this:
private static void AnalyzeModel(SemanticModelAnalysisContext semanticModelAnalysisContext)
{
var semanticModel = semanticModelAnalysisContext.SemanticModel;
var root = semanticModel.SyntaxTree.GetRoot();
// compare each using statement's name with its fully qualified name
foreach (var usingDirective in root.DescendantNodes().OfType<UsingDirectiveSyntax>())
{
var symbol = semanticModel.GetSymbolInfo(usingDirective.Name).Symbol;
var fullyQualifiedName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
if (fullyQualifiedName.Contains(GlobalTag))
{
fullyQualifiedName = fullyQualifiedName.Substring(GlobalTag.Length);
}
if (usingDirective.Name.ToString() != fullyQualifiedName)
{
// for each name that is not fully qualified, produce a diagnostic.
var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], symbol.Name);
semanticModelAnalysisContext.ReportDiagnostic(diagnostic);
}
}
}
The problem is the symbol.Locations[0] only contains items in metadata, not items in source. This leads to the following error:
Assert.IsTrue failed. Test base does not currently handle diagnostics in metadata locations.
My source in my unit tests looks like this:
private const string incorrectSourceCode = #"
namespace System
{
using IO;
using Threading;
}";
Why are there no items in symbol.Locations that are in source? Is there another place I can get this location? I've tried using symbol.ContainingSymbol.Locations[0] or symbol.ContainingNamespace.Locations[0], but those are not referring to the using specific using that I am interested in. I've been pulling out my hair over this for hours, and some clarity would be very greatly appreciated.
Thanks in advance!
Symbol contains MetadateLocation, so if you want to see SourceLocation just retrieve it from the appropriate SyntaxNode:
var diagnostic = Diagnostic.Create(Rule, usingDirective.Name.GetLocation(), symbol.Name)
instead of
var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], symbol.Name)

How to get unresolved symbols in Roslyn?

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();

Why can't I see my XML documentation when decompiling using ICSharpCode.Decompiler?

I've been looking into decompiling .dlls using ICSharpCode.Decompiler and found some sample code and fingers in the right direction on this thread:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/00e59445-9f85-4ec5-a04f-9796a72a00a2/library-to-decompile-assembly-to-c
I've copied the code and added set the variable ShowXmlDocumentation = true in the DecompilerSettings, however I'm still unable to see my documentation.
My source code looks like this:
var settings = new DecompilerSettings
{
FullyQualifyAmbiguousTypeNames = true,
ShowXmlDocumentation = true
};
const string assemblyName = "Experiments.Decompilation.dll";
var assembly1 = AssemblyDefinition.ReadAssembly(assemblyName);
var decompilerContext = new DecompilerContext(assembly1.MainModule) {Settings = settings};
var decompiler = new AstBuilder(decompilerContext);
decompiler.AddAssembly(assembly1);
var output = new StringWriter();
decompiler.GenerateCode(new PlainTextOutput(output));
var byteArray = Encoding.ASCII.GetBytes(output.ToString());
TextReader codeReader = new StreamReader(new MemoryStream(byteArray));
var line = codeReader.ReadToEnd();
Yet the variable line never has any of the expected XML documentation in it.
I get the full trace of what's in the .dll, for example
namespace Experiments.Decompilation
{
public interface ITest
{
long Method();
}
}
But I was expecting the interface method to have the XML docs I defined, a la
namespace Experiments.Decompilation
{
///<summary>
///This is some test documentation
///</summary>
public interface ITest
{
long Method();
}
}
But no cookie for me.
Am I missing anything? Do I need to change any other configuration?
If anyone has any ideas on this I'd really appreciate it. I've been wracking my brains and haven't found a solution myself, so here you are SO, please help!
Because that property doesn't actually do anything within the ICSharpCode.Decompiler library. It's just there to support projects (like ILSpy) that want to consume the decompiler.
ILSpy, for example, will check to see if the decompiler has the option set; if so, it will look up the appropriate XML file on-disk and parse the XMLDoc strings, and embed them in the final output.
Also, note that the actual .NET assembly doesn't have the XMLDoc in it. Visual Studio generates a separate file with that stuff, and if you don't have that, ILSpy won't be able to include XMLDoc even if you ask.

Categories