Using C# as a scripting language for a C# application - c#

I have developed an application that uses c# script files for certain configurations and settings. The script file contains various user generated objects and certain functions on those objects. Presently, the user has to generate a .cs file using a third party editor and supply the path to my program to make use of it. The disadvantage with this method is that the user does not have the flexibility of Auto-complete and intellisense-esque support while editing the script files.
I want to embed the script editing part into my application. I can do that using a rich-text editor. But coding the auto-complete part is a huge pain. Is there any way in which I can provide the user with an in-program editor that also does auto-complete....
Code for compiling a script dynamically in a program.
public String Compile(String inputfilepath)
{
CompilerResults res = null;
CSharpCodeProvider provider = new CSharpCodeProvider();
String errors = "";
if (provider != null)
{
try
{
Assembly asb = Assembly.Load("BHEL.PUMPSDAS.Datatypes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=81d3de1e03a5907d");
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false;
options.OutputAssembly = String.Format(outFileDir + oName);
options.GenerateInMemory = false;
options.TreatWarningsAsErrors = false;
options.ReferencedAssemblies.Add("System.dll");
options.ReferencedAssemblies.Add("System.Core.dll");
options.ReferencedAssemblies.Add("System.Xml.dll");
options.ReferencedAssemblies.Add("System.Xml.dll");
options.ReferencedAssemblies.Add(asb.Location);
res = provider.CompileAssemblyFromFile(options, inputfilepath);
errors = "";
if (res.Errors.HasErrors)
{
for (int i = 0; i < res.Errors.Count; i++)
{
errors += "\n " + i + ". " + res.Errors[i].ErrorText;
}
}
}
catch (Exception e)
{
throw (new Exception("Compilation Failed with Exception!\n" + e.Message +
"\n Compilation errors : \n" + errors + "\n"));
}
}
return errors;
}

Specifically for auto-complete, you will need to make use of two systems: a parser, and reflection.
A parser is a pretty straightforward concept, in theory, but I'm sure that it won't be easy to write for a language with as much syntactic sugar and as many context-sensitive keywords as C#.
Since .NET is inherently reflective, and provides a reflection framework, that part shouldn't be incredibly painful, either. Reflection allows you to manipulate the object-oriented elements comprising compiled assemblies--and the assemblies themselves--as objects. A method would be a Method object, for example. You can take a peek at this system by examining the members of the Type class, which provide one basic starting point for reflection. Another useful starting point is Assembly. MSDN, as usual, has a wealth of "official" information in a structured format.

Related

Load a usercontrol from a folder programatically

I was looking to load a usercontrol from a folder. I want people to be able to package an extension for my app. To do this, they'll have to create a c# user control and put the designer, code, and resx file into a folder. Then when they want to use their "extension", they'll select a folder from a folder selector (I have that) and my app will load their extension. I want to specifically pull out the user control and cast it into a usercontrol object. Am I able to do this and if so, how?
I looked around the internet and there doesn't really seem to be any close to this question. I wasn't able to create any script that could accomplish this. I don't even know where to start in this one. I know that I have to compile their usercontrol.
If this is not possible, the next best solution I can think of is maybe a precompiled usercontrol. If this is possible, how would I load that?
Any help would be appreciated. Thanks!
If you wish to compile the sources, this can be done using the System.CodeDom. Other than that you should load the types from the assembly and test to see if there is a UserControl in there, load it up and add it to your form.
Here is some example of what i described:
public void LoadPlugin(params string[] sourceCodeFilesForUserControl)
{
// Compile the source files
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.IncludeDebugInformation = true;
parameters.GenerateInMemory = true;
// Add references that they can use
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll"); // important for UserControl
parameters.TreatWarningsAsErrors = false;
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, sourceCodeFilesForUserControl);
if (results.Errors.Count > 0)
{
// Handle compile errors
StringBuilder sb = new StringBuilder();
foreach (CompilerError CompErr in results.Errors)
{
sb.AppendLine("Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";");
}
Console.Write(sb.ToString());
}
else
{
// The assembly we can search for a usercontrol
var assembly = results.CompiledAssembly;
// If the assembly was already compiled you might want to load it directly:
// assembly = Assembly.LoadFile(#"C:\Program Files\MyTool\plugins\someplugin.dll");
// Get the first type in the assembly that is a UserControl
var userControl = assembly.GetTypes().FirstOrDefault(x => x.BaseType == typeof(UserControl));
// Create a instance of the UserControl
var createdUserControl = Activator.CreateInstance(userControl, new object[] { }) as UserControl;
// Add the created UserControl to the current form
this.Controls.Add(createdUserControl);
}
}

Find references to loaded dlls

I have an application that can be customized by writing custom extensions. All of them are in proj\Extensions folder. At runtime my Core project loads every extension from the folder and executes code. The problem is when one of the extensions uses additional libraries, because the Core project cannot find reference to these additional libraries.
So for example in my Core project I have:
public void Preview(IFileDescription fileDescription)
{
var extension = Path.GetExtension(fileDescription.FilePath);
var reader = _readerFactory.Get(extension);
Data = reader.GetPreview(fileDescription);
}
In one of my extensions I have
public DataTable GetPreview(IFileDescription options)
{
var data = new DataTable();
using (var stream = new StreamReader(options.FilePath))
{
var reader = new CsvReader(stream); // <- This is from external library and because of this Core throws IO exception
}
/*
...
*/
return data;
}
Core only knows about the interface, so when one of readers uses for example CsvHelper.dll I get exception of FileNotFound, because Core cannot find CsvHelper.dll. Is there any way to tell the compiler to look for additional libraries in specific folder? I used Reference Paths, but it didn't solve the problem. It still threw the same exception.
Yes, it's possible. You can attach to the AppDomain.AssemblyResolve event and manually load the required DLLs from your add-in directory. Execute the following code before executing any add-in code:
var addinFolder = ...;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var missing = new AssemblyName(e.Name);
var missingPath = Path.Combine(addinFolder, missing.Name + ".dll");
// If we find the DLL in the add-in folder, load and return it.
if (File.Exists(missingPath))
return Assembly.LoadFrom(missingPath);
// nothing found, let .NET search the common folders
return null;
};

SharpShell server .dll NOT signed

I need to develop a Shell Context Menu extension that references some other custom assemblies... I don't want to assign a Strong Name Key to those custom assemblies!
The guide I followed to do this uses the SharpShell project and illustrates how to sign (but does not expalins why) the assembly... and this is my problem: if I sign my final .dll then I have many errors during my project's building phase, because some assemblies my project references are not strongly named ("Referenced assembly does not have a strong name").
In general, googling about the C# Shell Extension implementation, all best tutorials I found sign the final assembly... is it mandatory?
Without signing the assembly ServerManager.exe returns this error: "The file 'XYZ.dll' is not a SharpShell Server".
Finally I've solved my troubles... the SharpShell.dll file obtained through NuGet was a different version of the ServerManager.exe ones.
Uninstalling the SharpShell NuGet package and directly referencing the SharpShell.dll you find inside the ServerManager folder was my solution!
Moreover, I was looking between the article comments... please read this question.
You don't need to use old DLL.
Please use this code directly, without using ServerManager.exe.
private static ServerEntry serverEntry = null;
public static ServerEntry SelectedServerEntry
{
get
{
if (serverEntry == null)
serverEntry = ServerManagerApi.LoadServer("xxx.dll");
return serverEntry;
}
}
public static ServerEntry LoadServer(string path)
{
try
{
// Create a server entry for the server.
var serverEntry = new ServerEntry();
// Set the data.
serverEntry.ServerName = Path.GetFileNameWithoutExtension(path);
serverEntry.ServerPath = path;
// Create an assembly catalog for the assembly and a container from it.
var catalog = new AssemblyCatalog(Path.GetFullPath(path));
var container = new CompositionContainer(catalog);
// Get the exported server.
var server = container.GetExport<ISharpShellServer>().Value;
serverEntry.ServerType = server.ServerType;
serverEntry.ClassId = server.GetType().GUID;
serverEntry.Server = server;
return serverEntry;
}
catch (Exception)
{
// It's almost certainly not a COM server.
MessageBox.Show("The file '" + Path.GetFileName(path) + "' is not a SharpShell Server.", "Warning");
return null;
}
}
Install code:
ServerRegistrationManager.InstallServer(SelectedServerEntry.Server, RegistrationType.OS64Bit, true);
Register code:
ServerRegistrationManager.RegisterServer(SelectedServerEntry.Server, RegistrationType.OS64Bit);

SAXParser equivalent in C#

I have below java code , I need to convert these in C#, Kindly help me ..
public class Configuration {
private ConfigContentHandler confHandler;
public Configuration() {
}
public boolean parseConfigFile() throws Exception {
boolean bReturn = true;
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
System.out.println("*** Start parsing");
try {
confHandler = new ConfigContentHandler(100);
// Configuration file must be located in main jar file folder
// Set the full Prosper file name
String sConfigFile = "configuration.xml";
// Get abstract (system independent) filename
File fFile = new File(sConfigFile);
if (!fFile.exists()) {
System.out.println("Could not find configuration file " + sConfigFile + ", trying input parameters.");
bReturn = false;
} else if (!fFile.canRead()) {
System.out.println("Could not read configuration file " + sConfigFile + ", trying input parameters.");
bReturn = false;
} else {
parser.parse(fFile, confHandler);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Input error.");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("*** End parsing");
return bReturn;
}
Thanks
C# native XML parser XmlReader doesn't support SAX and is forward-only. You may take a look at this article presenting some specific points about it. You could simulate a SAX parser using XmlReader. If it doesn't suit your needs you could also use XDocument which is a different API for working with XML files in .NET. So to conclude there's no push XML parser built into .NET framework so you might need to use a third party library or COM Interop to MSXML to achieve this if you really need an event driven parser.
I used SAX for .NET in two projects successfully in the past.
http://saxdotnet.sourceforge.net/

Single-assembly multi-language Windows Forms deployment (ILMerge and satellite assemblies / localization) - possible?

I have a simple Windows Forms (C#, .NET 2.0) application, built with Visual Studio 2008.
I would like to support multiple UI languages, and using the "Localizable" property of the form, and culture-specific .resx files, the localization aspect works seamlessly and easily. Visual Studio automatically compiles the culture-specific resx files into satellite assemblies, so in my compiled application folder there are culture-specific subfolders containing these satellite assemblies.
I would like to have the application be deployed (copied into place) as a single assembly, and yet retain the ability to contain multiple sets of culture-specific resources.
Using ILMerge (or ILRepack), I can merge the satellite assemblies into the main executable assembly, but the standard .NET ResourceManager fallback mechanisms do not find the culture-specific resources that were compiled into the main assembly.
Interestingly, if I take my merged (executable) assembly and place copies of it into the culture-specific subfolders, then everything works! Similarly, I can see the main and culture-specific resources in the merged assemby when I use Reflector (or ILSpy). But copying the main assembly into culture-specific subfolders defeats the purpose of the merging anyway - I really need there to be just a single copy of the single assembly...
I'm wondering whether there is any way to hijack or influence the ResourceManager fallback mechanisms to look for the culture-specific resources in the same assembly rather than in the GAC and culture-named subfolders. I see the fallback mechanism described in the following articles, but no clue as to how it would be modified: BCL Team Blog Article on ResourceManager.
Does anyone have any idea? This seems to be a relatively frequent question online (for example, another question here on Stack Overflow: "ILMerge and localized resource assemblies"), but I have not found any authoritative answer anywhere.
UPDATE 1: Basic Solution
Following casperOne's recommendation below, I was finally able to make this work.
I'm putting the solution code here in the question because casperOne provided the only answer, I don't want to add my own.
I was able to get it to work by pulling the guts out of the Framework resource-finding fallback mechanisms implemented in the "InternalGetResourceSet" method and making our same-assembly search the first mechanism used. If the resource is not found in the current assembly, then we call the base method to initiate the default search mechanisms (thanks to #Wouter's comment below).
To do this, I derived the "ComponentResourceManager" class, and overrode just one method (and re-implemented a private framework method):
class SingleAssemblyComponentResourceManager :
System.ComponentModel.ComponentResourceManager
{
private Type _contextTypeInfo;
private CultureInfo _neutralResourcesCulture;
public SingleAssemblyComponentResourceManager(Type t)
: base(t)
{
_contextTypeInfo = t;
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture,
bool createIfNotExists, bool tryParents)
{
ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
if (rs == null)
{
Stream store = null;
string resourceFileName = null;
//lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
if (this._neutralResourcesCulture == null)
{
this._neutralResourcesCulture =
GetNeutralResourcesLanguage(this.MainAssembly);
}
// if we're asking for the default language, then ask for the
// invariant (non-specific) resources.
if (_neutralResourcesCulture.Equals(culture))
culture = CultureInfo.InvariantCulture;
resourceFileName = GetResourceFileName(culture);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
//save for later.
AddResourceSet(this.ResourceSets, culture, ref rs);
}
else
{
rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
}
}
return rs;
}
//private method in framework, had to be re-specified here.
private static void AddResourceSet(Hashtable localResourceSets,
CultureInfo culture, ref ResourceSet rs)
{
lock (localResourceSets)
{
ResourceSet objA = (ResourceSet)localResourceSets[culture];
if (objA != null)
{
if (!object.Equals(objA, rs))
{
rs.Dispose();
rs = objA;
}
}
else
{
localResourceSets.Add(culture, rs);
}
}
}
}
To actually use this class, you need to replace the System.ComponentModel.ComponentResourceManager in the "XXX.Designer.cs" files created by Visual Studio - and you will need to do this every time you change the designed form - Visual Studio replaces that code automatically. (The problem was discussed in "Customize Windows Forms Designer to use MyResourceManager", I did not find a more elegant solution - I use fart.exe in a pre-build step to auto-replace.)
UPDATE 2: Another Practical Consideration - more than 2 languages
At the time I reported the solution above, I was actually only supporting two languages, and ILMerge was doing a fine job of merging my satellite assembly into the final merged assembly.
Recently I started working on a similar project where there are multiple secondary languages, and therefore multiple satellite assemblies, and ILMerge was doing something very strange: Instead of merging the multiple satellite assemblies I had requested, it was merging the first satellite assembly in multiple times!
eg command-line:
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll
With that command-line, I was getting the following sets of resources in the merged assembly (observed with ILSpy decompiler):
InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!
After some playing around, I ended up realizing this is just a bug in ILMerge when it encounters multiple files with the same name in a single command-line call. The solution is simply to merge each satellite assembly in a different command-line call:
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll
When I do this, the resulting resources in the final assembly are correct:
InputProg.resources
InputProg.es.resources
InputProg.fr.resources
So finally, in case this helps clarify, here's a complete post-build batch file:
"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
IF %ERRORLEVEL% NEQ 0 GOTO END
"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll
IF %ERRORLEVEL% NEQ 0 GOTO END
del %1InputProg.exe
del %1InputProg.pdb
del %1TempProg.exe
del %1TempProg.pdb
del %1es\*.* /Q
del %1fr\*.* /Q
:END
UPDATE 3: ILRepack
Another quick note - One of the things that bothered me with ILMerge was that it is an additional proprietary Microsoft tool, not installed by default with Visual Studio, and therefore an extra dependency that makes it that little bit harder for a third party to get started with my open-source projects.
I recently discovered ILRepack, an open-source (Apache 2.0) equivalent that so far works just as well for me (drop-in replacement), and can be freely distributed with your project sources.
I hope this helps someone out there!
The only way I can see this working is by creating a class that derives from ResourceManager and then overriding the InternalGetResourceSet and GetResourceFileName methods. From there, you should be able to override where resources are obtained, given a CultureInfo instance.
A different approach:
1) add your resource.DLLs as embededed resources in your project.
2) add an event handler for AppDomain.CurrentDomain.ResourceResolve
This handler will fire when a resource cannot be found.
internal static System.Reflection.Assembly CurrentDomain_ResourceResolve(object sender, ResolveEventArgs args)
{
try
{
if (args.Name.StartsWith("your.resource.namespace"))
{
return LoadResourcesAssyFromResource(System.Threading.Thread.CurrentThread.CurrentUICulture, "name of your the resource that contains dll");
}
return null;
}
catch (Exception ex)
{
return null;
}
}
3) Now you have to implement LoadResourceAssyFromResource something like
private Assembly LoadResourceAssyFromResource( Culture culture, ResourceName resName)
{
//var x = Assembly.GetExecutingAssembly().GetManifestResourceNames();
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resName))
{
if (stream == null)
{
//throw new Exception("Could not find resource: " + resourceName);
return null;
}
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
var ass = Assembly.Load(assemblyData);
return ass;
}
}
Posted as answer since comments didn't provide enough space:
I couldn't find resources for neutral cultures (en instead of en-US) with the OPs solution. So I extended InternalGetResourceSet with a lookup for neutral cultures which did the job for me. With this you can now also locate resources which do not define the region. This is actually the same behaviour that the normal resourceformatter will show when not ILMerging the resource files.
//Try looking for the neutral culture if the specific culture was not found
if (store == null && !culture.IsNeutralCulture)
{
resourceFileName = GetResourceFileName(culture.Parent);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
}
This results in the following code for the SingleAssemblyComponentResourceManager
class SingleAssemblyComponentResourceManager :
System.ComponentModel.ComponentResourceManager
{
private Type _contextTypeInfo;
private CultureInfo _neutralResourcesCulture;
public SingleAssemblyComponentResourceManager(Type t)
: base(t)
{
_contextTypeInfo = t;
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture,
bool createIfNotExists, bool tryParents)
{
ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
if (rs == null)
{
Stream store = null;
string resourceFileName = null;
//lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
if (this._neutralResourcesCulture == null)
{
this._neutralResourcesCulture =
GetNeutralResourcesLanguage(this.MainAssembly);
}
// if we're asking for the default language, then ask for the
// invariant (non-specific) resources.
if (_neutralResourcesCulture.Equals(culture))
culture = CultureInfo.InvariantCulture;
resourceFileName = GetResourceFileName(culture);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//Try looking for the neutral culture if the specific culture was not found
if (store == null && !culture.IsNeutralCulture)
{
resourceFileName = GetResourceFileName(culture.Parent);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
}
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
//save for later.
AddResourceSet(this.ResourceSets, culture, ref rs);
}
else
{
rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
}
}
return rs;
}
//private method in framework, had to be re-specified here.
private static void AddResourceSet(Hashtable localResourceSets,
CultureInfo culture, ref ResourceSet rs)
{
lock (localResourceSets)
{
ResourceSet objA = (ResourceSet)localResourceSets[culture];
if (objA != null)
{
if (!object.Equals(objA, rs))
{
rs.Dispose();
rs = objA;
}
}
else
{
localResourceSets.Add(culture, rs);
}
}
}
}
I have a suggestion for part of your problem. Specifically, a solution to the step of updating .Designer.cs files to replace ComponentResourceManager with SingleAssemblyComponentResourceManager.
Move the InitializeComponent() method out of .Designer.cs and into the implementation file (include the #region). Visual Studio will continue to auto generate that section, with no problems as far as I can tell.
Use a C# alias at the top of the implementation file so that ComponentResourceManager is aliased to SingleAssemblyComponentResourceManager.
Unfortunately, I didn't get to test this fully. We found a different solution to our problem and so moved on. I hope it helps you though.
Just a thought.
You did the step and created your SingleAssemblyComponentResourceManager
So why do you take the pain to include your satellite assemblies in the ilmerged Assembly?
You could add the ResourceName.es.resx itself as a binary file to another resource in your project.
Than you could rewrite your code
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
with this code (not tested but should work)
// we expect the "main" resource file to have a binary resource
// with name of the local (linked at compile time of course)
// which points to the localized resource
var content = Properties.Resources.ResourceManager.GetObject("es");
if (content != null)
{
using (var stream = new MemoryStream(content))
using (var reader = new ResourceReader(stream))
{
rs = new ResourceSet(reader);
}
}
This should render the effort to include the sattelite assembiles in the ilmerge process obsolete.

Categories