在asp.net中创建动态表单c#

时间:2011-08-12 13:42:31

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

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

所以问题是这个应用程序已经作为所有客户端的所有可定制解决方案出售。所以,这个想法是他们想要的任何形式,我们可以使用数据库配置来构建它。问题是,这并不总是可行的,因为字段之间存在复杂的关系,而且表单本身之间存在复杂的关系。此外,只有一次代码库,这适用于多个客户端 - 所有客户端都是自己托管的。每个客户端都有非常特定的逻辑,它们在同一个代码库中是ALL,没有真正的分离。有时候使它变得通用太难了,所以有些情况下它有硬编码逻辑(如果formID = XXX则执行 _ )。您也可以在每个表单中拥有嵌套表单,例如,一组字段。

通常,当一个客户端请求更改时,我们进行更改并将其部署到该客户端 - 但是然后另一个客户端请求不同的更改,我们进行更改并为该客户端部署它,但是从早期的客户端打破它,并且很难尝试调试,因为一切都是动态的。我们无法回滚先前的更改,因为那时其他客户端将被搞砸。

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

我一直负责重组整个事情,这些是我的想法/问题:

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

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

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

  4. 我真的应该重写整件事吗?这个应用程序大约3岁,已经进行了大量的测试和事情,并且实施了严格的业务逻辑,所以我讨厌摆脱所有这些(joel的建议)。但它真的是一堆sphagetti代码,一切都需要永远做,而且由于微小的变化,事情一直在破碎。

  5. 我一直在阅读Martin Fowlers“Refactoring”和Michael Feathers“有效地使用遗留代码” - 而且它们很好,但我觉得它们是针对一个'稍微'更好的架构的应用程序编写的,它仍然是一个三层架构,并且存在“某种”逻辑相似性。

    思考/输入任何人?

    哦,还有“帮助!”

4 个答案:

答案 0 :(得分:4)

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

你可以/不可以做很多事情&在放弃3年的测试/开发之前,你是正确的思考。

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

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

答案 1 :(得分:4)

我当前的项目听起来几乎与您描述的产品完全相同。幸运的是,我在以前的产品上学到了大部分最难的课程,因此我能够以干净的方式开始我当前的项目。您应该阅读my answer to this question,其中描述了我的经历以及我学到的经验教训。

要关注的主要问题是您正在构建产品。如果您无法使用当前的产品功能集找到实现特定功能的方法,则需要花费一些额外的时间来考虑如何将此自定义一次性功能转换为可使所有功能受益的可配置功能(或者至少很多客户。

所以:

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

答案 2 :(得分:0)

由于您的应用程序似乎已成为一个很大的漏洞,完整(或几乎完全重写)可能是有道理的。

您还应该考虑面向文档的数据库(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或其他类型的字段类型轻松添加子表单。

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

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 :(得分:0)

这是一种常见但(IMO)有点天真的设计方法。 “而不是解决客户的问题,让我们建立一个工具,让他们解决自己的问题!”。但实际情况是,客户通常希望您解决他们的实际问题。因此,建立解决问题的方法。 如果您能够以允许您为不同客户重用某些部件的方式构建它,那很好。但这通常是框架已经为您完成的工作 - 找出应用程序所需的常用功能,并使它们以整洁的包装形式提供。