Windows Workflow Syntax Editor Integration Sample Question

SyntaxEditor .NET Languages Add-on for WPF Forum

Posted 10 years ago by sdever
Version: 13.2.0591
Avatar

We recently received the sample code that integrates the Windows Workflow Re-hosted Designer with the Syntax Editor control.  So far, we have been very impressed with the whole suite of WPF tools, documentation, the sample code provided, and specifically the Syntax Editor control with the .NET Languages Add-on.

We did encounter a problem with custom assemblies being loaded in the "projectAssembly.AssemblyReferences" in the Windows Workflow sample, and I am wondering if the forum could propose a solution.

The Issue

The Re-hosted Windows Workflow Designer uses IExpressionEditorService and IExpressionEditorInstance to allow replacing the default editor control with a different control.  In this case, we want to use the SyntaxEditor because of it's great syntax highlighting and intellisense.

When the Re-hosted Windows Workflow Designer detects the user gave focus to the editor control, it calls the IExpressionEditorService to request an Editor control instance.  A new IExpressionEditorInstance is created which encapsulates the SyntaxEditor control.  During the constructor, the code loads any required assemblies so that the SyntaxEditor .Net Languages Add-on can provide intellisense.  In the following example, I hard coded a custom dll.

private void DotNetProjectAssemblyReferenceLoader(object sender, DoWorkEventArgs e)
{
    // Add some common assemblies for reflection (any custom assemblies could be added using various Add overloads instead)
    projectAssembly.AssemblyReferences.AddMsCorLib();
    projectAssembly.AssemblyReferences.Add("System");
    projectAssembly.AssemblyReferences.Add("System.Core");
    projectAssembly.AssemblyReferences.Add("System.Xml");
    projectAssembly.AssemblyReferences.AddFrom(@"C:\Test\Custom.dll");
}

This works great the first time the IExpressionEditorInstance is created. 

But, if the user moves focus away from the editor control, and then back again...the Re-hosted Windows Workflow Desiger creates a new IExpressionEditorInstance.  Subsequent calls to the following code will fail.  

projectAssembly.AssemblyReferences.AddFrom(@"C:\Test\Custom.dll");  // Fails

I assume this is due to how the assembly is loaded for Reflection. (You may want to look at CodePlex: Common Complier Infrastructure for an alternative to Reflection.)

I was hoping that Ambient Assembly Repository created in the static constructor of the IExpressionEditorInstance would cache the data, but that does not seem to be working either.  The directory is created but I don't see any files generated.  (Edit:  The files are being generated.  I moved the Ambient Assembly Repository creation and purning to my applicaiton startup/shutdown.  Intellisense is still not working on the custom dll.)

Utlimately I will need to dynamically load and unload custom dlls into the SyntaxEditor control's projectAssembly based on what the user needs in the Windows Workflow.  Does anyone have a suggestions? 

[Modified 10 years ago]

Comments (8)

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello, thank you for evaluating SyntaxEditor.

Is the custom assembly changing continuously (what is the error)?  If not, one thing you can possibly do is just create a single static project assembly and use that in each editor instance.  That way it only loads once and is probably better anyhow since the editor instance will be getting created repeatedly.

We have a TODO list item to possibly look into using Cecil or CCI as an alternative to reflection.  We can't include those in the main product though because they are third-party, so they would need to be optional add-ons that just wrap the assembly loading and create the appropriate IBinaryAssembly.  That being said, the current method does work all right in most cases.


Actipro Software Support

Posted 10 years ago by sdever
Avatar

Question: Is the custom assembly changing?  
Answer:  In most cases the custom assembly will not be changing.  
(In theory, someone migh accidently select the wrong assembly when addng a reference and need to unload it, then browse to the correct assembly.  That could present a problem.  But in our case, the custom assemblies should not be changing.) 

Question:  What is the error?
Answer:  When the following code executes the first time, I have 5 items in the Assembly References collection.  When the code executes the second time, I only have 4 items in the collection.  No error is thrown.  The custom.dll is just not added.  I was hoping the the AddFrom method would detect that the assembly was already loaded in the process space and get the reflection data from the in process assembly, or better from the ambient assembly repository provider.

// Add some common assemblies for reflection (any custom assemblies could be added using various Add overloads instead)
projectAssembly.AssemblyReferences.AddMsCorLib();
projectAssembly.AssemblyReferences.Add("System");
projectAssembly.AssemblyReferences.Add("System.Core");
projectAssembly.AssemblyReferences.Add("System.Xml");
projectAssembly.AssemblyReferences.AddFrom(@"C:\Test\Custom.dll");

Question:  Could I use a Static Project Assembly?
Answer:  I agree that recreating the Project Assembly is not the best idea. I will expore a static Project Assembly.  

Note: I was able to work around the problem by creating my own method that handles Adding AssemblyReferences.  It first checks the ambient assembly repository cache path for a Dat file.  If it exist, I load the Dat, otherwise it loads the Dll.  As long as the Dll does not change and I don't prune the cache this seems to work.  This is similar to precreating the Dat files and loading the from a resource, but it happens at run time.

I was debating about trying to find a way to perform the AssemblyReferences.AddFrom in a different App Domain.  The advantage of this is I could populate the Cache and unload the App Domain to unload the assemblies.  This would give me a ReflectionOnly Load without actually loading the assembly.  But AppDomains are kind of a pain to work with.

Overall I am very impressed with the suite of controls.  Thank you.

[Modified 10 years ago]

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

I believe the ambient assembly repository provider stores the number of references for each assembly from the ProjectAssembly objects.  So if you drop all references to it, it will remove the assembly info from the assembly repository provider to free up memory.  Still it should be able to load the Custom.dll file again ok.  Unless there is some pruning or something going on at the time it's trying to load (perhaps a file lock issue)?  If you set VS to break on all thrown CLR exceptions, maybe you can catch what's going on there.


Actipro Software Support

Posted 10 years ago by sdever
Avatar

The exception is a FileLoadException.

API restriction: The assembly '' has already loaded from a different location. It cannot be loaded from a new location within the same appdomain.

My guess is the following method:

projectAssembly.AssemblyReferences.AddFrom()

 is reading the assembly into a byte array and then using a ReflectionOnlyLoad().

It is the same exception you get if you run this code.

using System.Reflection;
class Program
{
    static void Main(string[] args)
    {
        byte[] fileAsBytes = System.IO.File.ReadAllBytes(@"C:\Test\Custom.dll");
        Assembly.ReflectionOnlyLoad(fileAsBytes);
        Assembly.ReflectionOnlyLoad(fileAsBytes);  // exception here
    }
}

Just for contrast the following code, which uses ReflectionOnlyLoadFrom, DOES NOT throw an exception.

using System.Reflection;
class Program
{
    static void Main(string[] args)
    {   
        Assembly.ReflectionOnlyLoadFrom(@"C:\Test\Custom.dll");
        Assembly.ReflectionOnlyLoadFrom(@"C:\Test\Custom.dll");
    }
}

 

To duplicate this behavior in the Actipro and Windows Workflow Integration sample do the following.

1) Create a .Net dll with a namespace called "Test". Create a public class with a public method.  Call it Custom.dll.

2) Modify the sample code by adding one line to the DotNetProjectAssemblyReferenceLoader method.

projectAssembly.AssemblyReferences.AddFrom(@"C:\Test\Custom.dll");

 3) Run the program.

4) Give focus to the To part of the Assign activity and type:  new T    You should see the intellisense with your custom namespace.

5) Give focus to the Text part of the WriteLine Activity and type:  new T  You will not see your namespace because the assembly would not load for the new instance.

I have several options to workaround this issue. Overall the tool, documenation, and this forum has been outstanding.  Thank you.

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Thanks for the reply.

I believe we used to use ReflectionOnlyLoadFrom but then found that it locks the file.  So if someone tried to compile a new assembly at that path, it couldn't be written.  The ReadAllBytes version at least didn't lock the file.

One thing you might try is doing a shadow copy.  Perhaps copy the .dll to another new unique filename, load from that using our method, and then delete the file later on when done.

The best solution is to use one of those non-reflection ways to read the assembly info, and hopefully we can get one of those written in the future.


Actipro Software Support

Posted 10 years ago by Gerald Henson
Avatar

Would it be possible to receive the sample project integration with Windows Workflow? I am currently researching a solution to add intellisense to my rehosted workflow designer.

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Gerald,

Yes certainly.  Please email our support address and we'll send you the link.


Actipro Software Support

Posted 10 years ago by Gerald Henson
Avatar

Will do!

The latest build of this product (v24.1.2) was released 2 days ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.