C# 如何从XAML正确加载WF4工作流? 简短版本:

C# 如何从XAML正确加载WF4工作流? 简短版本:,c#,.net,workflow-foundation-4,workflow-foundation,C#,.net,Workflow Foundation 4,Workflow Foundation,如何从XAML加载WF4工作流? 重要细节:加载工作流的代码不需要事先知道工作流中使用了哪些类型 长版本: 我很难从VisualStudio创建的XAML文件加载WF4工作流。 我的场景是,我希望将此文件放入数据库,以便能够集中修改它,而无需重新编译工作流调用程序 我目前正在使用此代码: var xamlSchemaContext = new XamlSchemaContext(GetWFAssemblies()); var xmlReaderSettings = new XamlXmlRea

如何从XAML加载WF4工作流? 重要细节:加载工作流的代码不需要事先知道工作流中使用了哪些类型


长版本: 我很难从VisualStudio创建的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);
这给了我很多错误,分为两类:

类别1:
虽然我向
XamlSchemaContext
的构造函数提供了正确的程序集,但程序集中的类型未知

ValidationError{Message=处理表达式“GreetingActivationResult.ErrorPin”时遇到编译器错误。 未声明“GreetingActivationResult”。由于其保护级别,它可能无法访问。 ,Source=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:是否有其他方法可以消除这些验证错误,而无需事先知道使用了哪些名称空间和程序集

第二类:
我所有的输入参数都是未知的。我可以在activityBuilder.Properties中很好地看到它们,但是我仍然收到验证错误,说它们未知:

ValidationError{Message=处理表达式时遇到编译器错误 “别针”。 未声明“Pin”。由于其保护级别,可能无法访问该Pin。 ,Source=61:VisualBasicValue,PropertyName=,IsWarning=False}

目前尚无解决方案。

<强>问题2:< /强>如何告诉WF4使用XAML文件中定义的参数?< /P> < P>一个我知道同一个你正在尝试做的工作的系统是Team Foundation 2010的构建系统。在控制器上执行自定义生成工作流时,需要将生成控制器指向TFS中保存自定义程序集的路径。然后,控制器在开始处理工作流时从该位置递归加载所有程序集

您提到需要将文件保存在数据库中。在调用工作流之前,是否也可以将所需程序集的位置或元数据信息存储在同一数据库中,并使用反射递归加载它们

然后,您可以有选择地从此路径添加/删除程序集,而无需更改使用

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

方法。

我有一个这样做的项目-程序集也存储在数据库中

在实例化工作流实例时,我会执行以下操作:

  • 将程序集从数据库下载到缓存位置
  • 创建一个新的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);
                    }
                }
            }
    
    您的代理类需要实现MarshallByRefObject,并充当应用程序和新appdomain之间的通信链接

    我发现我能够毫无问题地加载工作流并获取根活动实例

    编辑2012年7月29日**

    即使只在数据库中存储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.Core
    • System.Xml
    总之:
    1.跟踪用户工作流引用的程序集(因为您将重新设置工作流设计器的主体位置,这将非常简单)
    2.构建将从中导入名称空间的程序集列表-这将是默认环境程序集和用户引用的程序集的联合。
    3.使用名称空间更新VisualBasicSettings并重新应用于根活动。

    您需要在执行工作流实例的项目和重新驻留工作流设计器的项目中执行此操作。

    问题2: 你不能执行ActivityBuilder,它只是为了设计。您必须加载DynamicActivity(仅通过ActivityXamlServices)。它应该是这样工作的(不使用特殊的XamlSchemaContext),但您必须事先加载所有使用过的程序集(将它们放置在bin目录中也应该可以,到目前为止,关于问题1,DynamicActivity可能会让事情变得更简单):

    总的来说,我得到的印象是您正在尝试实现自己的“ActivityDesigner”(如
            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);
                }
            }
    
                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);
    
    var dynamicActivity = ActivityXamlServices.Load(stream) as DynamicActivity;
    WorkflowInvoker.Invoke(dynamicActivity);
    
    //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");
            }
        }