【问题标题】:Dynamic form creation in asp.net c#在asp.net c#中创建动态表单
【发布时间】:2011-10-25 20:05:15
【问题描述】:

所以,我需要一些输入来重构一个 asp.net (c#) 应用程序,该应用程序基本上是一个用于创建动态表单(任何表单)的框架。从高层次的角度来看,有一个包含表单的表,然后有一个包含所有表单字段的表,两者之间是一对多的。有一个校验表,每个字段可以有多种校验,表单字段表到校验表是一对多的。

所以问题在于,该应用程序已作为万能的可定制解决方案出售给所有客户。所以,这个想法是他们想要的任何形式,我们可以使用数据库配置来构建它。问题是,这并不总是可能的,因为字段之间存在复杂的关系,表单本身之间也存在复杂的关系。此外,只有一个代码库,这是针对多个客户端的 - 所有客户端都自己托管它。每个客户端都有非常具体的逻辑,它们都在同一个代码库中,没有真正的分离。有时很难使其通用化,因此在某些情况下它具有硬编码逻辑(例如 if formID = XXX then do _)。您还可以有嵌套的表单,例如在每个表单中单独设置一组字段。

因此,通常,当一个客户请求更改时,我们进行更改并将其部署到该客户端 - 但随后另一个客户端请求不同的更改,我们进行更改并将其部署到该客户端,但是从早期的客户端破坏了它,尝试调试它很头疼,因为一切都是动态的。我们无法回滚之前的更改,因为那样另一个客户端就会被搞砸。

它不是在真正的 3 层架构中完成的——它是一个引用 DB 类和类库的网站。网站本身、类库中、数据库存储过程中都有业务逻辑(验证在存储过程中完成)。

我负责重新组织整个事情,以下是我的想法/问题:

  1. 我认为总的来说这是一个糟糕的模型,因为我听到一位开发人员说的其中一件事是,任何时候任何客户进行更改,我们都应该部署到每个人 - 但这是不现实的,如果我们说有 20 个客户 - 需要对一切进行回归测试,因为我们不知道影响...

  2. 总共大约有100种形式,它们之间有一些相似之处(不多)。但我认为动态引擎可以解决所有表单请求的想法也不现实。客户提出最奇怪的要求。例如,他们让这个引擎执行常规数据输入表单和搜索表单。

  3. 页面之间有很多保留状态,这一切都是使用会话变量完成的,这没关系,除了它没有真正被跟踪,因此来自同一用户的会话不断被覆盖,我认为应该取消会话。

  4. 我真的应该重写整个内容吗?这个应用程序大约有 3 年的历史,已经完成了很多测试和事情,并且实现了严肃的业务逻辑,所以我不想摆脱所有这些(乔尔的建议)。但它确实是一团乱七八糟的 sphagetti 代码,所有事情都需要很长时间才能完成,而且由于微小的变化,事情总是会中断。

我一直在阅读 Martin Fowlers “重构”和 Michael Feathers “有效地处理遗留代码” - 它们很好,但我觉得它们是为一个架构“稍微”更好的应用程序编写的,它仍然存在一个 3 层架构,逻辑上“有些”相似。

有什么想法/意见吗?

哦,还有“救命!”

【问题讨论】:

    标签: asp.net vb.net web-applications dynamic webforms


    【解决方案1】:

    这是一种常见但 (IMO) 有点幼稚的设计方法。 “与其解决客户的问题,不如打造一个工具,让他们自己解决问题!”。但现实情况是,客户通常希望您解决他们的实际问题。因此,构建解决他们问题的东西。 如果你能以一种允许你为不同的客户重复使用某些部分的方式来构建它,那很好。但这通常是框架已经为您完成的工作 - 找出应用程序所需的通用功能并将它们提供在简洁的包中。

    【讨论】:

    • 我同意,但我必须使用我所拥有的 - 你能扩展更多吗?
    • 虽然有一些话要说不是作为一名建筑宇航员 (joelonsoftware.com/articles/fog0000000018.html) 并且只是为客户提供他们需要的东西,但一旦您开始为多个客户提供服务,它真的可以得到回报建立一个架构,让您以最少的额外工作为每个客户的实施提供客户他们想要的东西。否则,一个客户的每个新修复或功能都会开始为其他三个客户引入错误。
    【解决方案2】:

    由于您的应用程序似乎已经变成一团大泥球,因此完全(或几乎完全重写)可能是有意义的。

    您还应该考虑新技术,例如面向文档的数据库(couchDB、MongoDB)

    大多数表单定义可能非常适合面向文档的数据库。例如:

    要定义客户表单,您可以使用如下所示的文档:

    {Type:"FormDefinition",
     EntityType: "Customer",
     Fields: [
       {FieldName:"CustomerName", 
        FieldType:"String", 
        Validations:[
          {ValidationType:"Required"},
          {ValidationType:"StringLength", Minimum:15, Maximum:50},
        ]},
        ...
       {FieldName:"CustomerType", 
        FieldType:"Dropdown", 
        PossibleValues: ["Standard", "Valued", "Gold"],
        DefaultValue: ["Standard"]
        Validations:[
          {ValidationType:"Required"},
          {
            ValidationType:"Custom", 
            ValidationClass:"MySystem.CustomerName.CustomValidations.CustomerStatus"
          }
        ]},
        ...
     ]
    };
    

    使用这种文档来定义您的表单,您可以轻松地添加特定于客户的表单和验证。

    您可以使用 SubForm 或其他字段类型轻松添加子表单。

    您可以为所有常见类型的字段定义 FieldTypes,例如电子邮件、电话号码、地址等。

    namespace System.CustomerName.CustomValidations {
      class CustomerStatus: IValidator {
    
        private FormContext form;
        private List<ValidationErrors> validationErrors;
    
        CustomerStatus(FormContext fc) {
          this.validationErrors = new List<ValidationErrors>();
          this.form = fc;
        }
    
        public List<ValidationErrors> Validate() {
          if (this.formContext.Fields["CustomerType"] == "Gold" && Int.Parse(this.form.Fields["OrderCount"]) < 10) { 
            this.validationErrors.Add(new ValidationError("A gold customer must have at least 10 orders"))
          }
    
          if (this.formContext.Fields["CustomerType"] == "Valued" && Int.Parse(this.form.Fields["OrderCount"]) < 5) { 
            this.validationErrors.Add(new ValidationError("A valued customer must have at least 5 orders"))
          }
          return this.validationErrors;          
        }
      }
    }
    

    具有该定义的文档记录可能如下所示:

    {Type:"Record",
     EntityType: "Customer",
     Fields: [
       {FieldName:"CustomerName", Value:"ABC Corp.", 
       {FieldName:"CustomerType", Value:"Gold",
       ...
     ]
    };
    

    当然,这个解决方案需要做很多工作,但是如果/当意识到它可以很容易地创建/更新/自定义表单。

    【讨论】:

      【解决方案3】:

      我当前的项目听起来与您描述的产品几乎完全相同。幸运的是,我从以前的产品中学到了大部分最艰难的课程,因此我能够从头开始我当前的项目。您可能应该阅读my answer to this question,它描述了我的经历和我学到的教训。

      主要关注的是您正在构建产品的想法。如果您找不到使用当前产品功能集实现特定功能的方法,则需要花一些额外的时间考虑如何将这种自定义一次性功能转变为可以使所有人受益的可配置功能(或最少)你的客户。

      所以:

      1. 如果您指的是能够创建完全可定制的表单的模型,该模型几乎不需要客户特定的代码,那么该模型是完全有效的,并且我有一个可维护的工作产品,真实的付费客户可以证明这一点.回归测试是针对特定的功能和配置组合执行的,而不是针对特定的客户端实现。使这成为可能的关键部分是:
        1. 一种管理界面,可有效禁止有问题的配置选项组合。
        2. 一个规则引擎,允许系统中的某些操作调用可自定义的触发器并导致其他操作发生。
        3. 一个集成框架,允许从各种来源提取数据并以可配置的方式推送到各种来源。
        4. 绝对必要时将自定义代码作为插件注入的选项。
      2. 是的,客户提出了奇怪的请求。通常值得建议替代解决方案,这些解决方案仍然可以解决客户的问题,同时仍然允许您的产品健壮且可配置以供其他客户使用。有时你只需要反击。有时您必须照他们说的做,但要使用明智的架构实践,以尽量减少这可能对其他客户端代码产生的影响。
      3. 尽量减少使用会话来跟踪状态。每个页面都应该有足够的信息来跟踪当前页面的状态。即使用户单击“返回”并开始执行其他操作,仍需要保留的信息应存储在数据库中。但是,我发现在会话中保留一种面包屑树,以跟踪用户如何到达特定位置以及完成后将他们带回何处是很有用的。但是他们当前实际所在的节点的 ID 需要逐页保存,并随着每个请求发送回来,因此当用户在不同选项卡中浏览不同页面时不会发生奇怪的事情.
      4. 使用增量重构。当你完成时,你可能最终会重写整个东西两次,或者你可能永远不会真正“完成”重构。但与此同时,一切仍然有效,而且您会时不时地拥有新功能。通常,重写整个内容会花费您认为需要的时间,因此不要试图一口气完成整个内容。

      【讨论】:

      • 感谢您的意见 - 让我感觉稍微好一点,因为我不必重写它......我并不期待。您对如何将这种“组合”字段输出到屏幕有何看法?由于它基于 UI,我的一部分人说在屏幕上输出控件的整个“循环”应该在 aspx.cs 中,但话又说回来,它的业务逻辑,我不想把它放在 aspx 中.cs
      • @MR:我会让业务逻辑完成所有业务端工作,确定要显示哪些字段并为每个字段提供一组显示信息,然后在你的 aspx 上隐藏代码文件只负责遍历这些字段并将这些选项转换为实际的前端 Web 表单控件。
      【解决方案4】:

      我有许多用于构建我支持的动态表单的类似应用程序。

      有很多事情可以/不可以做,在放弃 3 年的测试/开发之前认真思考是对的。

      我的意见是在你现有的基础上实现一个插件架构。表单的任何自定义代码都在插件中,并且该插件的名称与表单一起存储。生成表单时,会调用正确的插件来增强基本功能。这样您就可以将所有自定义代码移出现有库。这也应该意味着更少的重大更改,每个插件只影响它所附加的表单。

      从那时起,重构核心引擎将很容易,因为它是所有客户端和表单的通用功能。

      【讨论】:

      • 感谢您的输入 - 我将有一个类继承结构来处理此问题....您是否有与此相关的“插件”架构的具体示例?跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-24
      • 1970-01-01
      • 2010-12-18
      • 1970-01-01
      • 2017-08-11
      • 1970-01-01
      相关资源
      最近更新 更多