13

短縮版:

XAML から WF4 ワークフローを読み込むにはどうすればよいですか? 重要な詳細: ワークフローを読み込むコードは、ワークフローで使用される型を事前に知る必要はありません。


長いバージョン:

Visual Studio で作成された XAML ファイルから WF4 ワークフローを読み込むのに非常に苦労しています。私のシナリオは、このファイルをデータベースに配置して、ワークフロー インボーカーを再コンパイルせずに一元的に変更できるようにすることです。

私は現在このコードを使用しています:

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);

これにより、多くのエラーが発生します。エラーは次の 2 つのカテゴリに分類されます。

カテゴリ 1:
のコンストラクターに正しいアセンブリを提供しましたが、アセンブリの型が不明ですXamlSchemaContext

ValidationError { Message = 式 "GreetingActivationResult.WrongPin" の処理中にコンパイラ エラーが発生しました。「GreetingActivationResult」は宣言されていません。保護レベルにより、アクセスできない場合があります。、ソース = 10: VisualBasicValue、PropertyName = 、IsWarning = False }

これは、ここで説明されている手法を使用して解決できます。これは、基本的に、使用されているすべての型のアセンブリと名前空間をいくつかのVisualBasicSettingsインスタンスに追加します。

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

これは機能しますが、コードは使用されているすべての名前空間を認識する必要があるため、ワークフローの「動的読み込み」部分全体が冗談になります。
質問 1:どの名前空間とアセンブリが使用されているかを事前に知る必要なく、これらの検証エラーを取り除く別の方法はありますか?

カテゴリ 2:
すべての入力引数が不明です。それらは問題なく表示されますactivityBuilder.Propertiesが、不明であるという検証エラーが引き続き発生します。

ValidationError { Message = 式 "Pin" の処理中にコンパイラ エラーが発生しました。「ピン」は宣言されていません。保護レベルにより、アクセスできない場合があります。、ソース = 61: VisualBasicValue、PropertyName = 、IsWarning = False }

これまでのところ解決策はありません。
質問 2: XAML ファイルで定義された引数を使用するように WF4 に指示する方法を教えてください。

4

4 に答える 4

11

質問 2: 設計のためだけに ActivityBuilder を実行することはできません。DynamicActivity をロードする必要があります (ActivityXamlServices を介してのみ)。そのように (特別な XamlSchemaContext を使用せずに) 動作するはずですが、使用するすべてのアセンブリを事前に読み込んでおく必要があります (それらを bin ディレクトリに配置しても動作するはずです。質問 1 については、DynamicActivity を使用すると少し簡単になる可能性があります)。

var dynamicActivity = ActivityXamlServices.Load(stream) as DynamicActivity;
WorkflowInvoker.Invoke(dynamicActivity);

一般的に、独自の「ActivityDesigner」(VS など) を実装しようとしているという印象を受けました。私はこれを自分で試しましたが、DynamicActivity と ActivityBuilder を扱うのは非常に困難でした (DynamicActivity はシリアル化できませんが、ActivityBuilder は実行できません)。私の結果を見たい場合は、この記事の最後のセクションを読んでください。

于 2012-07-25T20:58:41.147 に答える
4

これを行うプロジェクトがあります-アセンブリもデータベースに保存されます。

ワークフロー インスタンスをインスタンス化するときは、次のことを行います。

  1. アセンブリをデータベースからキャッシュの場所にダウンロードします
  2. アセンブリ パスを渡す新しい AppDomain を作成します。
  3. 新しい 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 を名前空間で更新し、ルート アクティビティに再適用します。

これは、ワークフロー インスタンスを実行するプロジェクトと、ワークフロー デザイナーを再ホストするプロジェクトで行う必要があります。

于 2012-07-21T02:21:28.490 に答える
1

あなたがやろうとしているのと同じ仕事をする、私が知っている 1 つのシステムは、Team Foundation 2010 のビルド システムです。コントローラーでカスタム ビルド ワークフローを実行する場合、カスタム アセンブリを保持する TFS 内のパスをビルド コントローラーにポイントする必要があります。コントローラーは、ワークフローの処理を開始するときに、その場所からすべてのアセンブリを再帰的に読み込みます。

ファイルをデータベースに保持する必要があると述べました。ワークフローを呼び出す前に、必要なアセンブリに関する場所またはメタ データ情報を同じデータベースに保存し、Reflection を使用してそれらを再帰的にロードすることはできませんか?

その後、このパスからアセンブリを選択的に追加/削除できます。

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

アプローチ。

于 2012-07-19T13:42:09.427 に答える