I'm trying to load a Lua script into my project at runtime. However, whenever I try to do so, I receive the following error when loading the file:
Items\food.lua:1: unexpected symbol near '・
The file itself is simple enough, but I can't see the cause of this:
config = {
itemtype = 2,
name = "Food",
basevalue = 1,
inventorytexture = 0,
red = 255,
green = 255,
blue = 255,
inventorywidth = 1,
inventoryheight = 1
}
Here is the code that I am using to run this in C#, in case this has anything to do with it:
// In main initialisation
public void ItemSetup()
{
itemTemplates.Add(new ItemTemplate("Items\\food.lua"));
}
// constructor for ItemTemplate class
Lua LuaLoader = new Lua();
public ItemTemplate(string filename)
{
LuaLoader.DoFile(filename); // here is where the error comes up
}
I have tried changing the settings for the file itself, however none of these appear to help. Here are the settings that I have for this file:
Build Action: None
Copy to Output Directory: Copy of newer
Custom Tool: <blank>
Custom Tool Namespace: <blank>
File Name: food.lua
Full Path: [...]\Items\food.lua
Looks like it could be a byte order mark. See: How do I remove the BOM character from my xml file and XML - Data At Root Level is Invalid
I've dealt with it in some XML files before, so I wouldn't be surprised if this is the same issue. As pointed out in some of the linked questions, there are utilities available to remove/avoid them easily as part of your workflow.
Related
I'm asking this because I want to eliminate the "Failed to insert item" warnings in a Unity project, which is caused by amount of scripts exceeding a certain value. Here is a solution provided by unity, which suggests applying [AddComponentMenu("")]attribute to all monobehaviours accordingly.
Since I'm working on a project with lots of scripts, it seems very difficult to apply this attribute manually. I need to:
Finding all classes under \Assets\Scripts (or in Assembly-CSharp) who inherit monobehaviour or any of its child class
Applying [AddComponentMenu("PATH")] attibute to the classes found above and put the realtive path of the script in the "PATH"
I don't know how to implement these and have been failing to find a solution by myself.
Some additional features would be wonderful (not necessary):
If there is existing [AddComponentMenu("")] in the script, do not override the original one.
Automatically applying [AddComponentMenu("")] to the scripts to be created.
Looking forward to all kinds of help. Thanks a lot!
There are probably many ways to do this outside of Unity. However, I will assume you want to do it all in Unity itself just for the simpleness of things.
Remark: There are for sure edge-cases left but I hope this is a good starting point
Finding all classes under \Assets\Scripts (or in Assembly-CSharp) who inherit monobehaviour or any of its child class
This first point would be quite simple - kind of. The Assets don't allow to find script types themselves but you can get all script assets by searching for t:MonoScript in the search bar in the Assets view.
The same can be done also via script using AssetDatabase.FindAssets like e.g.
var scriptGUIDs = AssetDataBase.FindAssets($"t:{nameof(MonoScript)}", new[] {"Assets/Scripts"});
From there you will need to load the script asset and check its type using e.g.
foreach (var scriptGUID in scriptGUIDs)
{
// e.g. Assets/Scripts/MyComponent.cs
var scriptAssetPath = AssetDatabase.GUIDToAssetPath(scriptGUID);
// first get the actual asset
var scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptAssetPath);
// get the system type
var scriptType = scriptAsset.GetClass();
// Now first of all check if this is actually something derived from MonoBehaviour -> if not skip
if(!scriptType.IsSubclassOf(typeof(MonoBehaviour))) continue;
.....
}
Applying [AddComponentMenu("PATH")] attribute to the classes found above and put the realtive path of the script in the "PATH"
This is of course way more tricky since it requires to find the according code line (right above the class implementation) and add text into the file.
So this requires multiple steps
Check if this type already has the attribute -> skip, which also solves
If there is existing [AddComponentMenu("")] in the script, do not override the original one.
Load the raw text content
find the line of code which contains public class TYPENAME :
Insert your attribute line before that line
Write all lines back to the file
After all done refresh the data base to cause a reload/recompilation
This could look somewhat like e.g.
// Find all assets of type "MonoScript" in the folder Assets/Scripts
// or remove /Scripts if you really want to go for all in the Assets
var scriptGUIDs = AssetDatabase.FindAssets($"t:{nameof(MonoScript)}", new[] {"Assets/Scripts"});
foreach (var scriptGUID in scriptGUIDs)
{
// e.g. Assets/Scripts/MyComponent.cs
var scriptAssetPath = AssetDatabase.GUIDToAssetPath(scriptGUID);
// first get the actual asset
var scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptAssetPath);
// get the system type
var scriptType = scriptAsset.GetClass();
// Now first of all check if this is actually something derived from MonoBehaviour -> if not skip
if(!scriptType.IsSubclassOf(typeof(MonoBehaviour))) continue;
// check if this class already has the attribute -> if so skip
if(scriptType.IsDefined(typeof(AddComponentMenu), true)) continue;
// otherwise load in the lines as a list so we can insert our new line
var lines = File.ReadAllLines(scriptAssetPath).ToList();
// find the one defining the class
var indexOfClassLine = lines.FindIndex(l => l.Contains($" class {scriptType.Name} : "));
// e.g. Assets/Scripts/MyComponent
// => you might want to e.g. also remove the "Assets" part ;)
var relativePathWithoutExtension = scriptAssetPath.Substring(0, scriptAssetPath.Length - 3);
// insert the new attribute one line right above that line
lines.Insert(indexOfClassLine, $"[AddComponentMenu(\"{relativePathWithoutExtension}\")]");
File.WriteAllLines(scriptAssetPath, lines);
}
AssetDatabase.Refresh();
And finally
Automatically applying [AddComponentMenu("")] to the scripts to be created.
Somewhere in you project you need an implementation of AssetModificationProcessor which will basically do the same thing as before, just this time it is slightly easier since we don't need to check the type since all c# scrips created via the menu are MonoBehaviour by default
public class ScriptKeywordProcessor : UnityEditor.AssetModificationProcessor
{
public static void OnWillCreateAsset(string path)
{
// ignore meta files
var tempPath = path.Replace(".meta", "");
var index = tempPath.LastIndexOf(".");
if (index < 0) return;
// ignore if not a .cs script
var file = tempPath.Substring(index);
if (file != ".cs") return;
// otherwise load in the lines as a list so we can insert our new line
var lines = File.ReadAllLines(path).ToList();
// find the one defining the class
var indexOfClassLine = lines.FindIndex(l => l.Contains($" class {Path.GetFileNameWithoutExtension(path)} : "));
// e.g. Assets/Scripts/MyComponent
// => you might want to e.g. also remove the "Assets" part ;)
var relativePathWithoutExtension = path.Substring(0, path.Length - 3);
// insert the new attribute one line right above that line
lines.Insert(indexOfClassLine, $"[AddComponentMenu(\"{relativePathWithoutExtension}\")]");
File.WriteAllLines(path, lines);
AssetDatabase.Refresh();
}
}
I'm writing a visual studio extension based on the Concord Samples Hello World project. The goal is to let the user filter out stack frames by setting a list of search strings. If any of the search strings are in a stack frame, it is omitted.
I've got the filter working for a hardcoded list. That needs to be in a non-package-based dll project in order for the debugger to pick it up. And I have a vsix project that references that dll with an OptionPageGrid to accept the list of strings. But I can't for the life of me find a way to connect them.
On the debugger side, my code looks something like this:
DkmStackWalkFrame[] IDkmCallStackFilter.FilterNextFrame(DkmStackContext stackContext, DkmStackWalkFrame input)
{
if (input == null) // null input frame indicates the end of the call stack. This sample does nothing on end-of-stack.
return null;
if (input.InstructionAddress == null) // error case
return new[] { input };
DkmWorkList workList = DkmWorkList.Create(null);
DkmLanguage language = input.Process.EngineSettings.GetLanguage(new DkmCompilerId());
DkmInspectionContext inspection = DkmInspectionContext.Create(stackContext.InspectionSession, input.RuntimeInstance, input.Thread, 1000,
DkmEvaluationFlags.None, DkmFuncEvalFlags.None, 10, language, null);
string frameName = "";
inspection.GetFrameName(workList, input, DkmVariableInfoFlags.None, result => GotFrameName(result, out frameName));
workList.Execute();
CallstackCollapserDataItem dataItem = CallstackCollapserDataItem.GetInstance(stackContext);
bool omitFrame = false;
foreach (string filterString in dataItem.FilterStrings)
{
if (frameName.Contains(filterString))
{
omitFrame = true;
}
}
The CallstackCollapserDataItem is where I theoretically need to retrieve the strings from user settings. But I don't have access to any services/packages in order to e.g. ask for WritableSettingsStore, like in You've Been Haacked's Example. Nor can I get my OptionPageGrid, like in the MSDN Options Example.
The other thing I tried was based on this StackOverflow question. I overrode the LoadSettingsFromStorage function of my OptionPageGrid and attempted to set a static variable on a public class in the dll project. But if that code existed in the LoadSettingsFromStorage function at all, the settings failed to load without even entering the function. Which felt like voodoo to me. Comment out the line that sets the variable, the breakpoint hits normally, the settings load normally. Restore it, and the function isn't even entered.
I'm at a loss. I really just want to pass a string into my Concord extension, and I really don't care how.
Ok, apparently all I needed to do was post the question here for me to figure out the last little pieces. In my CallstackCollapserDataItem : DkmDataItem class, I added the following code:
private CallstackCollapserDataItem()
{
string registryRoot = DkmGlobalSettings.RegistryRoot;
string propertyPath = "vsix\\CallstackCollapserOptionPageGrid";
string fullKey = "HKEY_CURRENT_USER\\" + registryRoot + "\\ApplicationPrivateSettings\\" + propertyPath;
string savedStringSetting = (string)Registry.GetValue(fullKey, "SearchStrings", "");
string semicolonSeparatedStrings = "";
// The setting resembles "1*System String*Foo;Bar"
if (savedStringSetting != null && savedStringSetting.Length > 0 && savedStringSetting.Split('*').Length == 3)
{
semicolonSeparatedStrings = savedStringSetting.Split('*')[2];
}
}
vsix is the assembly in which CallstackCollapserOptionPageGrid is a DialogPage, and SearchStrings is its public property that's saved out of the options menu.
I try to load an image from imgres32.dll. I'm trying to do it like this:
Load the dll:
dll_h = LoadLibrary(#"C:\Windows\System32\imgres32.dll");
Pass the handle to my function which does the ressource loading:
Bitmap b = GetImageResource(dll_h, "1002");
The function looks like this:
static Bitmap GetImageResource(IntPtr handle, string resourceId)
{
IntPtr img_ptr = NativeMethods.LoadImage(handle, resourceId, IMAGE_BITMAP, 0, 0, 0);
if (img_ptr == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception((int)NativeMethods.GetLastError());
return Image.FromHbitmap(img_ptr);
}
No matter which parameters I enter, I always get error code 1813 meaning
The specified resource type cannot be found in the image file.
When I open the file in Visual Studio, I see a folder called Icon containing an Image with id 1002.
When I click it, it shows me several Bitmap images contained, in different resolutions, containing one with resolution 16 x 16. But when I call
LoadImage(handle, resourceId, IMAGE_BITMAP, 16, 16, 0);
Neither this not any other parameter combination does work, I always get error 1813.
IMAGE_BITMAP is a constant int set to 0 like documented here, same with IMAGE_ICON and IMAGE_CURSOR but none of them works.
Help is very much appreciated. Thanks.
You should prefix the resource Id with #. Call it this way:
GetImageResource(dll_h, "#1002");
I'm currently working on a VSTO project for which I have 5 .settings files:
Settings.settings (Default)
s201213.settings
s201314.settings
s201415.settings
s201516.settings
Over time there will be more settings files included following the same naming convention ('s' followed by a tax year).
I know I can iterate through a settings file, but is there a way to iterate through the actual settings files themselves?
I've tried things such as:
public void Example()
{
System.Collections.IEnumerator testSetting = MyAddIn.Properties.s201213.Default.Properties.GetEnumerator();
while (testSetting.MoveNext())
{
System.Diagnostics.Debug.WriteLine("Setting:\t" + testSetting.Current.ToString());
}
}
Which obviously only iterates through a single settings file, but I can't seem to figure out the logic of iterating through all the settings files as a collection, without explicitly naming each one in the code. I hope that makes sense, any help is appreciated.
Update:
I think I'm getting somewhere with the following code:
foreach(Type test in Assembly.GetExecutingAssembly().GetTypes())
{
if (System.Text.RegularExpressions.Regex.IsMatch(test.Name, "^s[0-9]{6}$"))
{
PropertyInfo value = test.GetProperty("LEL");
try
{
System.Diagnostics.Debug.WriteLine("Name:\t" + test.Name +
"\nNameSpace:\t" + test.Namespace +
"\nProperty:\t" + test.GetProperty("LEL").ToString() +
"\n");
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
}
Which seems to be recognising the settings files and the stored values:
Output:
Name: s201213
NameSpace: MyAddIn.Properties
Property: Double LEL
Name: s201314
NameSpace: MyAddIn.Properties
Property: Double LEL
Name: s201415
NameSpace: MyAddIn.Properties
Property: Double LEL
Name: s201516
NameSpace: MyAddIn.Properties
Property: Double LEL
However I can't seem to get the actual value of the "LEL" setting which should return a Double?
2nd Update
I've actually given up and decided to use a local DB instead - but I would still like to know if this is possible, and I think other people would like to know too so I'm going to throw a bounty at it to try and generate some interest.
Jeremy's answer got me to the finish post, but thought I'd post the final code I used so that it can be seen in context:
public void GetLEL()
{
var fileMap = new ConfigurationFileMap(AppDomain.CurrentDomain.BaseDirectory + #"CustomAddIn.dll.config");
var configuration = ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
var sectionGroup = configuration.GetSectionGroup("userSettings");
var section = (ClientSettingsSection)sectionGroup.Sections.Get("MyAddIn.s201213");
var setting = section.Settings.Get("LEL");
System.Diagnostics.Debug.WriteLine(setting.Value.ValueXml.InnerXml);
// Prints "107" as expected.
}
The answer is really simple once you see it (no need to iterate through Types nor use System.IO directory):
using System.Configuration; //Add a reference to this DLL
...
var fileMap = new ConfigurationFileMap(Application.StartupPath + #"\GetSettingFilesValues.exe.config");
var configuration = ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
var sectionGroup = configuration.GetSectionGroup("applicationSettings"); // This is the section group name, change to your needs, ie application or user
var section = (ClientSettingsSection)sectionGroup.Sections.Get("GetSettingFilesValues.Properties.s201415"); //This is the section name, change to your needs (you know the tax years you need)
var setting = section.Settings.Get("LEL");
System.Diagnostics.Debug.WriteLine(setting.Value.ValueXml.InnerXml);
I agree with your approach to use the dB, though I'm sure this will benefit other people too.
I believe you can use the System.IO namespace classes for iterating through files with the same extension. There is no built-in mechanisms or properties for that.
Short version:
How do I load a WF4 workflow from XAML?
Important detail: The code that loads the workflow shouldn't need to know beforehand which types are used in the workflow.
Long version:
I am having a very hard time loading a WF4 workflow from the XAML file create by Visual Studio.
My scenario is that I want to put this file into the database to be able to modify it centrally without recompiling the Workflow invoker.
I am currently using this code:
var xamlSchemaContext = new XamlSchemaContext(GetWFAssemblies());
var xmlReaderSettings = new XamlXmlReaderSettings();
xmlReaderSettings.LocalAssembly = typeof(WaitForAnySoundActivity).Assembly;
var xamlReader = ActivityXamlServices.CreateBuilderReader(
new XamlXmlReader(stream, xmlReaderSettings),
xamlSchemaContext);
var activityBuilder = (ActivityBuilder)XamlServices.Load(xamlReader);
var activity = activityBuilder.Implementation;
var validationResult = ActivityValidationServices.Validate(activity);
This gives me a whole lot of errors, which fall into two categories:
Category 1:
Types from my assemblies are not known, although I provided the correct assemblies to the constructor of XamlSchemaContext.
ValidationError { Message = Compiler error(s) encountered processing expression "GreetingActivationResult.WrongPin".
'GreetingActivationResult' is not declared. It may be inaccessible due to its protection level.
, Source = 10: VisualBasicValue, PropertyName = , IsWarning = False }
This can be solved by using the technique described here, which basically adds the assemblies and namespaces of all used types to some VisualBasicSettings instance:
var settings = new VisualBasicSettings();
settings.ImportReferences.Add(new VisualBasicImportReference
{
Assembly = typeof(GreetingActivationResult).Assembly.GetName().Name,
Import = typeof(GreetingActivationResult).Namespace
});
// ...
VisualBasic.SetSettings(activity, settings);
// ... Validate here
This works but makes the whole "dynamic loading" part of the Workflow a joke, as the code still needs to know all used namespaces.
Question 1: Is there another way to get rid of these validation errors without the need to know beforehand which namespaces and assemblies are used?
Category 2:
All my input arguments are unknown. I can see them just fine in activityBuilder.Properties but I still get validation errors saying they are unknown:
ValidationError { Message = Compiler error(s) encountered processing expression
"Pin".
'Pin' is not declared. It may be inaccessible due to its protection level.
, Source = 61: VisualBasicValue, PropertyName = , IsWarning = False }
No solution so far.
Question 2: How to tell WF4 to use the arguments defined in the XAML file?
Question 2:
You can´t execute an ActivityBuilder, it´s just for design. You have to load a DynamicActivity (only through ActivityXamlServices). It should work that way (without using a special XamlSchemaContext), but you must have loaded all used assemblies in advance (placing them in the bin directory should also work, so far about Question 1, DynamicActivity might make things a little bit easier):
var dynamicActivity = ActivityXamlServices.Load(stream) as DynamicActivity;
WorkflowInvoker.Invoke(dynamicActivity);
In general, I got the impression that you´re trying to implement your own "ActivityDesigner" (like VS). I tried this myself, and it was quite hard to deal with DynamicActivity and ActivityBuilder (as DynamicActivity is not serializable but ActivityBuilder cannot be executed), so I ended up with an own activity type that internally converts one type into the other. If you want to have a look at my results, read the last sections of this article.
I have a project that does this - the assemblies are also stored in a database.
When it is time to instantiate a workflow instance I do the following:
Download the assemblies from the database to a cache location
Create a new AppDomain passing the assembly paths into it.
From the new AppDomain load each assembly - you may also need to load assemblies required by your hosting environment too.
I didn't need to mess around with VisualBasic settings - at least as far as I can see having taken a quick look in my code but I'm sure I've seen it somewhere...
In my case while I don't know the input names or types, the caller is expected to have built a request that contains the input names and values (as strings) which are then converted into the correct types via a reflection helper class.
At this point I can instantiate the workflow.
My AppDomain initialisation code looks like this:
/// <summary>
/// Initializes a new instance of the <see cref="OperationWorkflowManagerDomain"/> class.
/// </summary>
/// <param name="requestHandlerId">The request handler id.</param>
public OperationWorkflowManagerDomain(Guid requestHandlerId)
{
// Cache the id and download dependent assemblies
RequestHandlerId = requestHandlerId;
DownloadAssemblies();
if (!IsIsolated)
{
Domain = AppDomain.CurrentDomain;
_manager = new OperationWorkflowManager(requestHandlerId);
}
else
{
// Build list of assemblies that must be loaded into the appdomain
List<string> assembliesToLoad = new List<string>(ReferenceAssemblyPaths);
assembliesToLoad.Add(Assembly.GetExecutingAssembly().Location);
// Create new application domain
// NOTE: We do not extend the configuration system
// each app-domain reuses the app.config for the service
// instance - for now...
string appDomainName = string.Format(
"Aero Operations Workflow Handler {0} AppDomain",
requestHandlerId);
AppDomainSetup ads =
new AppDomainSetup
{
AppDomainInitializer = new AppDomainInitializer(DomainInit),
AppDomainInitializerArguments = assembliesToLoad.ToArray(),
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
PrivateBinPathProbe = null,
PrivateBinPath = PrivateBinPath,
ApplicationName = "Aero Operations Engine",
ConfigurationFile = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory, "ZenAeroOps.exe.config")
};
// TODO: Setup evidence correctly...
Evidence evidence = AppDomain.CurrentDomain.Evidence;
Domain = AppDomain.CreateDomain(appDomainName, evidence, ads);
// Create app-domain variant of operation workflow manager
// TODO: Handle lifetime leasing correctly
_managerProxy = (OperationWorkflowManagerProxy)Domain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().GetName().Name,
typeof(OperationWorkflowManagerProxy).FullName);
_proxyLease = (ILease)_managerProxy.GetLifetimeService();
if (_proxyLease != null)
{
//_proxyLease.Register(this);
}
}
}
The download assemblies code is easy enough:
private void DownloadAssemblies()
{
List<string> refAssemblyPathList = new List<string>();
using (ZenAeroOpsEntities context = new ZenAeroOpsEntities())
{
DbRequestHandler dbHandler = context
.DbRequestHandlers
.Include("ReferenceAssemblies")
.FirstOrDefault((item) => item.RequestHandlerId == RequestHandlerId);
if (dbHandler == null)
{
throw new ArgumentException(string.Format(
"Request handler {0} not found.", RequestHandlerId), "requestWorkflowId");
}
// If there are no referenced assemblies then we can host
// in the main app-domain
if (dbHandler.ReferenceAssemblies.Count == 0)
{
IsIsolated = false;
ReferenceAssemblyPaths = new string[0];
return;
}
// Create folder
if (!Directory.Exists(PrivateBinPath))
{
Directory.CreateDirectory(PrivateBinPath);
}
// Download assemblies as required
foreach (DbRequestHandlerReferenceAssembly dbAssembly in dbHandler.ReferenceAssemblies)
{
AssemblyName an = new AssemblyName(dbAssembly.AssemblyName);
// Determine the local assembly path
string assemblyPathName = Path.Combine(
PrivateBinPath,
string.Format("{0}.dll", an.Name));
// TODO: If the file exists then check it's SHA1 hash
if (!File.Exists(assemblyPathName))
{
// TODO: Setup security descriptor
using (FileStream stream = new FileStream(
assemblyPathName, FileMode.Create, FileAccess.Write))
{
stream.Write(dbAssembly.AssemblyPayload, 0, dbAssembly.AssemblyPayload.Length);
}
}
refAssemblyPathList.Add(assemblyPathName);
}
}
ReferenceAssemblyPaths = refAssemblyPathList.ToArray();
IsIsolated = true;
}
And finally the AppDomain initialisation code:
private static void DomainInit(string[] args)
{
foreach (string arg in args)
{
// Treat each string as an assembly to load
AssemblyName an = AssemblyName.GetAssemblyName(arg);
AppDomain.CurrentDomain.Load(an);
}
}
Your proxy class needs to implement MarshalByRefObject and serves as your communication link between your app and the new appdomain.
I find that I am able to load workflows and get the root activity instance without any problem.
EDIT 29/07/12 **
Even if you only store the XAML in the database you will need to track the referenced assemblies. Either your list of referenced assemblies will tracked in an additional table by name or you will have to upload (and obviously support download) the assemblies referenced by the workflow.
Then you may simply enumerate all the reference assemblies and add ALL namespaces from ALL public types to the VisualBasicSettings object - like this...
VisualBasicSettings vbs =
VisualBasic.GetSettings(root) ?? new VisualBasicSettings();
var namespaces = (from type in assembly.GetTypes()
select type.Namespace).Distinct();
var fullName = assembly.FullName;
foreach (var name in namespaces)
{
var import = new VisualBasicImportReference()
{
Assembly = fullName,
Import = name
};
vbs.ImportReferences.Add(import);
}
VisualBasic.SetSettings(root, vbs);
Finally don't forget to add namespaces from the environment assemblies - I add namespaces from the following assemblies:
mscorlib
System
System.Activities
System.Core
System.Xml
So in summary:
1. Track the assembly referenced by the user's workflow (since you will be rehosting the workflow designer this will be trivial)
2. Build a list of assemblies from which namespaces will be imported - this will be a union of the default environment assemblies and the user referenced assemblies.
3. Update the VisualBasicSettings with the namespaces and reapply to the root activity.
You will need to do this in the project that executes workflow instances and in the project that rehosts the workflow designer.
One system that I know which does the same job that you are trying to do is the Team Foundation 2010's build system. When you execute a custom build workflow on a controller, you need to point the build controller to a path in TFS where you keep your custom assemblies. The controller then recursively loads up all the assemblies from that location as it starts processing the workflow.
You mentioned that you need to keep the file in a database. Can you not also store the location or meta data information about the required assemblies in the same database and use Reflection to load them recursively before you invoke your workflow?
You can then selectively add/remove assemblies from this path without having to alter the code that dynamically load assemblies using the
var settings = new VisualBasicSettings();
settings.ImportReferences.Add(new VisualBasicImportReference
{
Assembly = typeof(GreetingActivationResult).Assembly.GetName().Name,
Import = typeof(GreetingActivationResult).Namespace
});
// ...
VisualBasic.SetSettings(activity, settings);
// ... Validate here
approach.
This is the way how I load xaml embeded resource (default workflow) to a Workflow Designer:
//UCM.WFDesigner is my assembly name,
//Resources.Flows is the folder name,
//and DefaultFlow.xaml is the xaml name.
private const string ConstDefaultFlowFullName = #"UCM.WFDesigner.Resources.Flows.DefaultFlow.xaml";
private void CreateNewWorkflow(object param)
{
//loading default activity embeded resource
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ConstDefaultFlowFullName))
{
StreamReader sReader = new StreamReader(stream);
string content = sReader.ReadToEnd();
//createion ActivityBuilder from string
ActivityBuilder activityBuilder = XamlServices.Load( ActivityXamlServices
.CreateBuilderReader(new XamlXmlReader(new StringReader(content)))) as ActivityBuilder;
//loading new ActivityBuilder to Workflow Designer
_workflowDesigner.Load(activityBuilder);
OnPropertyChanged("View");
}
}