これを行うプロジェクトがあります-アセンブリもデータベースに保存されます。
ワークフロー インスタンスをインスタンス化するときは、次のことを行います。
- アセンブリをデータベースからキャッシュの場所にダウンロードします
- アセンブリ パスを渡す新しい AppDomain を作成します。
- 新しい AppDomain から各アセンブリをロードします。ホスティング環境で必要なアセンブリもロードする必要がある場合があります。
VisualBasic の設定をいじる必要はありませんでした。少なくともコードをざっと見てみる限りでは、どこかで見たことがあるはずです...
私の場合、入力の名前や型はわかりませんが、呼び出し元は、入力の名前と値 (文字列として) を含むリクエストを作成し、リフレクション ヘルパー クラスを介して正しい型に変換する必要があります。
この時点で、ワークフローをインスタンス化できます。
私の AppDomain 初期化コードは次のようになります。
/// <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);
}
}
}
ダウンロード アセンブリ コードは非常に簡単です。
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;
}
最後に、AppDomain の初期化コード:
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);
}
}
プロキシ クラスは MarshalByRefObject を実装する必要があり、アプリと新しい appdomain 間の通信リンクとして機能します。
ワークフローを読み込んでルート アクティビティ インスタンスを問題なく取得できることがわかりました。
編集 29/07/12 **
データベースに XAML のみを保存する場合でも、参照されているアセンブリを追跡する必要があります。参照されたアセンブリのリストが追加のテーブルで名前別に追跡されるか、ワークフローによって参照されるアセンブリをアップロードする (そして明らかにダウンロードをサポートする) 必要があります。
次に、すべての参照アセンブリを列挙し、すべてのパブリック型からすべての名前空間を VisualBasicSettings オブジェクトに追加するだけです。このように...
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);
最後に、環境アセンブリから名前空間を追加することを忘れないでください。次のアセンブリから名前空間を追加します。
- mscorlib
- システム
- System.Activities
- System.Core
- System.Xml
要約すると:
1. ユーザーのワークフローによって参照されるアセンブリを追跡します (ワークフロー デザイナーを再ホストするので、これは簡単です)
2. 名前空間がインポートされるアセンブリのリストを作成します - これはデフォルトのユニオンになります環境アセンブリとユーザー参照アセンブリ。
3. VisualBasicSettings を名前空間で更新し、ルート アクティビティに再適用します。
これは、ワークフロー インスタンスを実行するプロジェクトと、ワークフロー デザイナーを再ホストするプロジェクトで行う必要があります。