可配置/动态参数

时间:2014-09-15 15:21:11

标签: c# xml dynamic parameters workflow

我想构建类似管道的东西,我可以将命令(转换器,读取器,编写器......)放在一起,就像线性工作流程一样 并通过命令的给定参数连接它们。一切都应该是可序列化的,并且必须使用干净的xml, 因为第一步没有计划图形编辑器,所以编辑了xml(在VS中使用生成的模式和智能感知支持这并不痛苦。)

示例:

<DataCommand name="AboFileReader"> 
<connection>
  <parameter name="filename"  direction="in"  type="string">
  <parameter name="tabledata" direction="out" type="DataTable">
</connection>       
</DataCommand>
<DataCommand name="TrimConverter"> 
<connection>
  <param name="tabledata" direction="in"  type="DataTable" >
  <param name="tabledata" direction="out" type="DataTable" >
</connection> 
</DataCommand>     
<DataCommand name="AboDataConverter"> 
<connection>
  <param name="tabledata" direction="in"  type="DataTable"  >
  <param name="tabledata" direction="out"  type="DataTable" >
</connection> 
</DataCommand> 
<DataCommand name="AboSqlWriter"> 
<connection>
  <param name="tabledata"  direction="in"  type="DataTable" >
</connection> 
</DataCommand>

我可以通过三种不同的方式来实现它,所有这些方式都有利有弊,我不知道选择哪种方式。

首先我认为1.是要走的路,但后来我提出了解决方案3.因为它提供了一个干净的xml并且是本机可序列化的。 对于下一步(编辑器),绑定到GUI应该很容易,而做反射的属性网格不是我的第一选择(反射总是很难看),有一组参数可以很容易地绑定到GUI。

也许组合1和3是要在启动时收集集合中的所有参数?

1。数据注释

[Parameter(Direction = "In", Description="...")]
public string FileName {get; set;}

[Parameter(Direction = "Out", Description="...")]
public DataTable Table {get; set;}

优点: +本机参数被暴露 +编码时的intellisense

缺点: - 原生序列化? - 丑陋的xml?

2。 Windows Workflow Foundation样式

public InParameter<string> FileName {get; set;}

public OutParameter<DataTable> Table {get; set;}

优点: 编码时+ intellisense +参数被暴露 +序列化工作

缺点: - 丑陋的xml?

第3。集合填写了构造函数

ObservableCollection<Parameter> Parameters {get; set;}

public Init()
{
  Parameters.Add( new Parameter() { Name="FileName", Type = typeOf(string),    Direction="In",  Description="..." } );
  Parameters.Add( new Parameter() { Name="Table",    Type = typeOf(DataTable), Direction="Out", Description="..." } );
}

优点: +序列化工作 + clean xml

缺点: - 参数未在源代码中公开 - 编码时无智能感知

您的经历是什么,前进的方向是什么?

1 个答案:

答案 0 :(得分:0)

就个人而言,我会选择一个不那么详细的XML表示,例如:

<ReadFile filename="..." />
<Trim />
<Convert />
<WriteSQL />

我假设大多数操作都有特定的输入和输出类型,所以除非这是可配置的,否则不需要拼写出来。这消除了一堆属性,但不是全部,所以你的问题仍然存在。理想情况下,您需要的是:

  1. 在代码结构中可见 - 这可用作文档,并让IDE帮助您
  2. 易于序列化 - 没有重复的,容易出错的代码
  3. 易于以序列化形式阅读 - 因此您需要干净的XML
  4. 由于数字1,您将要使用属性。由于数字2,您需要自动确定需要序列化哪些属性 - 反射所在的位置。由于在数字3中,您只想序列化那些可配置的字段 - 因此您需要一些东西来区分这些属性。

    使用属性来指示要公开的属性是一个好主意,因为它清楚地传达了他们的意图。它还允许您忽略其他属性,这样可以保持序列化格式的清晰。您也可以选择仅序列化公共属性,但属性可以提供更大的灵活性。最糟糕的是,它们会使代码更加混乱。

    以下是我如何设置基础:

    public interface IOperation
    {
        Type InputType { get; }
        Type OutputType { get; }
    
        object GetOutput(object input);
    }
    

    每个操作都提供自己的IOperation接口实现:

    public class ReadFileOperation : IOperation
    {
        [Parameter]
        public string Filename { get; set; }
    
        public Type InputType { get { return null; } } // No input
        public Type OutputType { get { return typeof(DataTable); } }
    
        public object GetOutput(object input)
        {
            // TODO: Read file into DataTable here!
        }
    }
    

    如果您的操作可以配置为使用不同的输入或输出类型,那么提供InputTypeOutputType setter和[Parameter]属性应该可以正常工作。< / p>

    您可以编写单个函数来查找实现IOperation的所有类型,并存储每种类型的属性列表。然后,在解析或写入时,您将查找当前类型并使用其属性列表。这个函数只需要调用一次,如果性能确实是一个问题,你可以使用它的输出在你的应用程序启动时生成解析/编写代码,但除非我真的不得不这样做,否则我不会这样做

    public Dictionary<Type, List<PropertyInfo>> DetectParameterProperties()
    {
        Dictionary<Type, List<PropertyInfo>> lookup = new Dictionary<Type, List<PropertyInfo>>();
    
        IEnumerable<Type> operations = Assembly
            .GetAssembly(typeof(IOperation))
            .GetTypes()
            .Where(t => t.IsClass && !t.IsAbstract && typeof(IOperation).IsAssignableFrom(t));
    
        foreach (Type operation in operations)
        {
            lookup[operation] = new List<PropertyInfo>();
    
            IEnumerable<PropertyInfo> parameters = operation.GetProperties().Where(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(ParameterAttribute)));
            foreach (PropertyInfo parameter in parameters)
                lookup[operation].Add(parameter);
        }
    
        return lookup;
    }
    

    请注意,随着您提供更多配置选项,错误处理变得更加重要。一个简单的类型检查,以确保所有输入和输出类型匹配是好的,但有些操作可能需要自己检查,以确保它们不会以其他方式配置错误。