Is there a way to "cap" RoslynPad's Roslyn's IntelliSense? - c#

I'm actually integrating the amazing RoslynPad into a WinForms application and working damn well.
The point of the integration is allowing the user to type in some C# code so it can be used in a future.
Thing is I'm interested on "capping" the user so he could just use some System or even LinQ functions. I don't want to allow the user to think he is allowed to use System.IO and others. Of course I can't prevent him/her typing System.IO.File.Delete, but will surely help if the System.IO's Assembly is not loaded into the RoslynPad's IntelliSense.
The source code typed by the user is going to be compiled locally before being saved into the DB. I'm adding just a few and necessary Assemblies for the compilation, so if System.IO it won't compile, of course.
As I explained, I just want to cap the Intellisense, so they don't think they have access to almost the whole .NET Framework.
EDIT: Added the actual implementation actually done. I'm loading "RoslynPad.Roslyn.Windows" and "RoslynPad.Editor.Windows" assemblies to the editor.
private RoslynCodeEditor _editor;
private void InitializeEditor(string sourceCode)
{
if (string.IsNullOrWhiteSpace(sourceCode))
sourceCode = string.Empty;
_editor = new RoslynCodeEditor();
var workingDirectory = Directory.GetCurrentDirectory();
var roslynHost = new RoslynHost(additionalAssemblies: new[]
{
Assembly.Load("RoslynPad.Roslyn.Windows"),
Assembly.Load("RoslynPad.Editor.Windows")
});
_editor.Initialize(roslynHost, new ClassificationHighlightColors(), workingDirectory, sourceCode);
_editor.FontFamily = new System.Windows.Media.FontFamily("Consolas");
_editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
_editor.FontSize = 12.75f;
elementHost1.Child = _editor;
this.Controls.Add(elementHost1);
}

You can use pass a RoslynHostReferences instance to the RoslynHost constructor, and decide which assemblies and namespaces are imported by default.
You could use the same logic as Default, just remove System.IO.Path from the type list.
Note that System.IO is not an assembly, but rather a namespace, which is in the core library, so there's no simple way to completely remove it.

Related

Forcing .Extensions namespace to load when embedded DLL will need it

I'm building a utility that uses Microsoft's DACPAC libraries. For the purpose of this tool, I want to embed all requisite libraries in the executable. It appears that when I execute DacServices.GenerateDeployScript() it's trying to use the Microsoft.SqlServer.Dac.Extensions library. The library is also embedded, but perhaps isn't being resolved with my EventHandler the way other DLLs are. My EventHandler is like this:
private static Assembly ResolveEventHandler(Object sender, ResolveEventArgs args)
{
//Debugger.Break();
String dllName = new AssemblyName(args.Name).Name + ".dll";
var assem = Assembly.GetExecutingAssembly();
String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName));
if (resourceName == null) return null;
using (var stream = assem.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
This works for resolving other items, but I believe that the likely issue is that the Microsoft.SqlServer.Dac namespace is making an execution time call to the .Extensions namespace and isn't able to resolve the namespace or the methods in it. I could be wrong, but I'm not sure what else could be the cause.
The calls to methods and classes in .Dac itself are being handled fine, so I know the EventHandler is working properly. I'm not really sure what to do and would appreciate any guidance. I've tried using Microsoft.SqlServer.Dac.Extenions at the top of the .cs file, but since I don't directly call anything in that namespace, it's grey and probably is ignored by the compiler.
Thanks!
Update:
I made a call to the .Extensions namespace in the code to force it to be read into memory prior to the failing call, though it appears that it already was. I set a breakpoint where the resolver kicks off. Just prior to it failing, it's trying to resolve .resource for each DLL, e.g. Microsoft.SqlServer.Dac.resource and Microsoft.SqlServer.TransactSql.ScriptDom.resource - all for DLLs embedded in the executable. The resolver doesn't see anything because there are no .resource files in the project, so nothing compiled into the manifest. Aren't these supposed to just be resident in memory while a DLL is being utilized? When the DLLs are all present in the same directory as the .exe, it functions fine, and also doesn't create temporary .resource files in the directory, so I'm unsure what I'm looking to resolve.
Update 2:
Using a PDB of the DAC libraries, it appears the failing line is:
IOperation operation = DacServices.CreateDeploymentArtifactGenerationOperation(OperationResources.GenerateDeployScriptCaption, (ErrorManager errorManager) => this.CreatePackageToDatabaseDeployment(package.PackageSource, targetDatabaseName, dacDeployOption, errorManager), (IDeploymentController controller, DeploymentPlan plan, ErrorManager errorManager) => DacServices.WriteDeploymentScript(streamWriter, controller, plan, errorManager), cancellationToken1, dacLoggingContext);
And the resulting exceptions are:
The extension type Microsoft.SqlServer.Dac.Deployment.Internal.InternalDeploymentPlanExecutor could not be instantiated.
and
The extension type Microsoft.SqlServer.Dac.Deployment.Internal.InternalDeploymentPlanModifier could not be instantiated.

How to output a file from a roslyn code analyzer?

I'm using the roslyn API to write a DiagnosticAnalyzer and CodeFix.
After I have collected all strings and string-interpolations, I want to write all of them to a file but I am not sure how to do this the best way.
Of course I can always simply do a File.WriteAllText(...) but I'd like to expose more control to the user.
I'm also not sure about how to best trigger the generation of this file, so my questions are:
I do not want to hard-code the filename, what would be the best way to expose this setting to the user of the code-analyzer? A config file? If so, how would I access that? ie: How do I know the directory?
If one string is missing from the file, I'd like to to suggest a code fix like "Project contains changed or new strings, regenerate string file". Is this the best way to do this? Or is it possible to add a button or something to visual studio?
I'm calling the devenv.com executable from the commandline to trigger builds, is there a way to force my code-fix to run either while building, or before/after? Or would I have to "manually" load the solution with roslyn and execute my codefix?
I've just completed a project on this. There are a few things that you will need to do / know.
You will probably need to switch you're portable class library to a class library. otherwise you will have trouble calling the File.WriteAllText()
You can see how to Convert a portable class library to a regular here
This will potentially not appropriately work for when trying to apply all changes to document/project/solution. When Calling from a document/project/solution, the changes are precalcuated and applied in a preview window. If you cancel, an undo action is triggered to undo all changes, if you write to a file during this time, and do not register an undo action you will not undo the changes to the file.
I've opened a bug with roslyn but you can handle instances by override the preview you can see how to do so here
And one more final thing you may need to know is how to access the Solution from the analyzer which, Currently there is a hack I've written to do so here
As Tamas said you can use additional files you can see how to do so here
You can use additional files, but I know on the version I'm using resource files, are not marked as additional files by default they are embeddedResources.
So, for my users to not have to manually mark the resource as additonalFiles I wrote a function to get out the Designer.cs files associated with resource files from the csproj file using xDoc you can use it as an example if you choose to parse the csproj file:
protected List<string> GetEmbeddedResourceResxDocumentPaths(Project project)
{
XDocument xmldoc = XDocument.Load(project.FilePath);
XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003";
var resxFiles = new List<string>();
foreach (var resource in xmldoc.Descendants(msbuild + "EmbeddedResource"))
{
string includePath = resource.Attribute("Include").Value;
var includeExtension = Path.GetExtension(includePath);
if (0 == string.Compare(includeExtension, RESX_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
{
var outputTag = resource.Elements(msbuild + LAST_GENERATED_TAG).FirstOrDefault();
if (null != outputTag)
{
resxFiles.Add(outputTag.Value);
}
}
}
return resxFiles;
}
For config files you can use the AdditionalFiles msbuild property, which is passed to the analyzers through the context. See here.

Dynamically compiled project losing resources

I need to compile source code of big project dynamically and output type can be Windows Application or Class Library.
Code is nicely executed and its possible to make .dll or .exe files, but problem is that, when I'm trying to make .exe file - it's losing resources like project icon. Result file doesn't include assembly information to.
Any way to solve this? (Expected result should be the same, that manual Build function on project file in Visual Studio 2015).
Thank you!
var workspace = MSBuildWorkspace.Create();
//Locating project file that is WindowsApplication
var project = workspace.OpenProjectAsync(#"C:\RoslynTestProjectExe\RoslynTestProjectExe.csproj").Result;
var metadataReferences = project.MetadataReferences;
// removing all references
foreach (var reference in metadataReferences)
{
project = project.RemoveMetadataReference(reference);
}
//getting new path of dlls location and adding them to project
var param = CreateParamString(); //my own function that returns list of references
foreach (var par in param)
{
project = project.AddMetadataReference(MetadataReference.CreateFromFile(par));
}
//compiling
var projectCompilation = project.GetCompilationAsync().Result;
using (var stream = new MemoryStream())
{
var result = projectCompilation.Emit(stream);
if (result.Success)
{
/// Getting result
//writing exe file
using (var file = File.Create(Path.Combine(_buildPath, fileName)))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
}
We never really designed the workspace API to include all the information you need to emit like this; in particular when you're calling Emit there's an EmitOptions you can pass that includes, amongst other things, resource information. But we don't expose that information since this scenario wasn't hugely considered. We've done some of the work in the past to enable this but ultimately never merged it. You might wish to consider filing a bug so we officially have the request somewhere.
So what can you do? I think there's a few options. You might consider not using Roslyn at all but rather modifying the project file and building that with the MSBuild APIs. Unfortunately I don't know what you're ultimately trying to achieve here (it would help if you mentioned it), but there's a lot more than just the compiler invocation that is involved in building a project. Changing references potentially changes other things too.
It'd also be possible, of course, to update MSBuildWorkspace yourself to pass this through. If you were to modify the Roslyn code, you'll see we implement a series of interfaces named "ICscHostObject#" (where # is a number) and we get passed the information from MSBuild to that. It looks like we already stash that in the command line arguments, so you might be able to pass that to our command line parser and get the data back you need that way.

Is it possible to patch a dotnet function on the fly

Recently I found an architect-often-used .net program has implemented a function wrongly. So I successfully patched it using ILSpy and Reflexil in a static way of modifying binaries. However, it is annoying that you need to patch it and remove StrongNameCheck again when new minor version releases. (btw, the author believes it is a feature instead of a bug)
Hopefully, the program fully supports assemblies as a plugin. And my target is a public non-static member function in a public class which can be directly called by plugins. Is there a way to patch the function on the fly?
I usually use some APIHook tricks in unmanaged C++ but dotnet is really a different thing. In this case, I want the modification still valid after my assembly unloads (so more similar to a patch, not a hook).
Yes you can do it with code injection. But you need to know some MSIL. There is also a library for that which is called Mono.Cecil.
Here is a example code
Console.WriteLine("> INJECTING INTO 12345.EXE..." + Environment.NewLine);
AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(#"C:\dummy.exe");
var writeLineMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
var writeLineRef = asm.MainModule.Import(writeLineMethod);
var pStartMethod = typeof(Process).GetMethod("Start", new Type[] { typeof(string) });
var pStartRef = asm.MainModule.Import(pStartMethod);
foreach (var typeDef in asm.MainModule.Types)
{
foreach (var method in typeDef.Methods)
{
//Let's push a string using the Ldstr Opcode to the stack
method.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldstr, "INJECTED!"));
//We add the call to the Console.WriteLine() method. It will read from the stack
method.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Call, writeLineRef));
//We push the path of the executable you want to run to the stack
method.Body.Instructions.Insert(2, Instruction.Create(OpCodes.Ldstr, #"calc.exe"));
//Adding the call to the Process.Start() method, It will read from the stack
method.Body.Instructions.Insert(3, Instruction.Create(OpCodes.Call, pStartRef));
//Removing the value from stack with pop
method.Body.Instructions.Insert(4, Instruction.Create(OpCodes.Pop));
}
}
asm.Write("12345.exe"); //Now we just save the new assembly
Don't monkey patch code. Add the functionality to your code base and call that function. Or write an adapter class that wraps the underlying assembly, which is much neater.
If the author of the code thinks it's not a bug then it may be in there for reasons you don't understand and could be part of any number of bug fixes.

Release the lock on dlls picked up by MEF in a click once application

How do I enable in my ClickOnce Application (WPF) such plugin dlls can be delted at runtime?
I have found an old demo that do it with this.
private static void SetShadowCopy()
{
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain.CurrentDomain.SetCachePath(#"C:\MEF\PartUpdatesInPlace\PartUpdatesInPlace\bin\Debug\Cache");
}
But it yields warnings that it is obsolete and I should use something else but I cant figure out how to enable shadow copying when not doing like above.
The two lines works as expected. And I have seen people doing a shell exe application that startst he real application. I dont want that either.
I assume there must be a way to do the same as above but not using obsolete methods.
Update
// Works
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain.CurrentDomain.SetCachePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + #"\Cache\");
// Dont Work
AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles = "true";
AppDomain.CurrentDomain.SetupInformation.CachePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + #"\Cache\";
The difference seems to be that SetShadowCopyFiles() works from the point its called and after, so when I setup the Mef Catalogs after this calls the dlls works and I can update dlls and do a container.refresh() without problems.
Using SetupInformation no cache folder is created and the dlls are locked.
Update 2
To others. This dont work for click once applications so far.
According to MSDN, the alternative to SetShadowCopyFiles() is IAppDomainSetup.ShadowCopyFiles {get;set;}
You should just be able to set it to true and give the same functionality.
To use it, set the ShadowCopyFiles property on the SetupInformation. AppDomain.ShadowCopyFiles is read-only.
AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles = "true";
EDIT: It appears as though changing the SetupInformation after the domain has been created has no effect.
AppDomainSetup Class: Changing the properties of an AppDomainSetup
instance does not affect any existing AppDomain. It can affect only
the creation of a new AppDomain, when the CreateDomain method is
called with the AppDomainSetup instance as a parameter.
I am unsure how you are supposed to do it now other than continuing with the obsolete methods and risking the fact that the next iteration of .NET may remove them.
EDIT 2: I played around with this a little bit and used dotPeek to see what the AppDomain was doing with the ShadowCopyFiles. I decided to see if I could set it through Reflection.
You may want to try the below and see if this does what you need. Setting it to true on the internal FusionStore property of AppDomain causes the AppDomain.ShadowCopyFiles property to reflect the change which was not the case when setting it on the exposed SetupInformation.ShadowCopyFiles property.
var setupInfoProperty = AppDomain.CurrentDomain.GetType().GetProperty("FusionStore", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance);
var setupInfo = (AppDomainSetup) setupInfoProperty.GetValue(AppDomain.CurrentDomain);
setupInfo.ShadowCopyFiles = "true";
setupInfo.CachePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + #"\Cache\";

Categories