2009-05-29 181 views
24

这是一个通用设计问题:如何在ASP.NET MVC中实现动态(运行时生成的)表单?ASP.NET MVC中的动态(运行时生成的)表单

这里的情况:

  1. 甲网站管理员可以定义形式参数(字段,字段的类型,验证)与GUI(MVC视图)。
  2. 根据需要,运行时根据管理配置为最终用户生成表单。我假设所有这些逻辑都驻留在控制器中 - 或者是扩展方法,动作过滤器或类似的东西。
  3. 最终用户填写表格,点击提交,信息被捕获到数据库中。

定制不需要支持嵌套控件,第三方控件等等,但我怀疑一个非常优雅的设计会允许这样做。大多数情况下,我只需要管理员能够将其他字段指定为文本框,复选框,单选按钮和组合框。我还需要应用程序为数据分配一个空间以保存在数据库中,但我相信我已经找到了这一部分。

感谢您的帮助。

+0

我反对结束这个问题。在特定的服务器端框架*上生成运行时生成的表单*的体系结构非常广泛,但是如下面的答案所证明的,它显然是可以回答的。所有架构问题都只是因为架构决策是高层次的,有利有弊吗? – 2017-08-18 18:11:13

回答

1

这样做的一种方法是创建您自己的ModelBinder,这将是您生成的表单的核心。 modelbinder负责验证ModelState并重建类型为ViewDataModel(假设您的视图已输入)。

DataAnnotations模型绑定可能是此通过AttributesViewDataModel一个很好的参考此自定义模型绑定器可以让你做的是描述属性的验证(在UI呈现提示)。但是,这是所有定义的编译时间,但是可以开始编写自定义模型绑定器。

在你的情况下,你的模型联编程序应该从xml文件/字符串获得运行时字段的验证。

如果你有这样一个路径:

routes.MapRoute(null, "Forms/{formName}/", new { action = "Index", controller = "Forms", formName = ""}), 

然后,你可以找到正确的形式的XML FormsController.Index(string formName),并把它传递给视图。

FormsModel应该包含所有可能的方法来获取数据我没有看到任何其他方式。 Xml可以映射到函数名称(可能是参数),您可以使用FormsModel上的反射来调用ViewData或输入ViewDataModel与数据。

窗体索引视图可以通过HtmlHelper扩展生成一个表单,该表单需要XmlDocument

然后,当您(或asp.net mvc)将您的表单绑定到您的ViewData您的自定义模型联编程序被调用时,它可以检查当前控制器值以查找formName并查找相应的保存所有验证规则的xml 。然后ModelBinder负责填写ModelState任何运行时定义的错误。

这是一个艰巨的任务,但是当在我看来:)

更新一个更好的替代模型数据将是一个非常松散的数据库架构大卫利德尔指出被拉断成功地值得的。我仍然经历了将它保存为xml(或其他序列化格式)的麻烦,并使用它来生成视图并保存自定义的验证规则,以便您可以更好地控制每个字段的布局和验证。

3

另一种选择是有一个非常松散耦合的数据库模式。

 
//this will contain all the fields and types that the admin user sets 
**ApplicationFields** 
FieldName 
FieldType 
... 

//these are all the values that have some mapping to a ParentObjectID 
**FormValues** 
ParentObjectID 
FieldName 
FieldValue 

当您提交通过你的FormCollection产生你的运行视图(从ApplicationFields)然后就循环和尝试,并把它放在你需要更新ParentObject。

 
public ActionResult MyForm(FormCollection form) 
{ 
    //this is the main object that contains all the fields 
    var parentObject; 

    foreach (string key in form) 
    { 
     parentObject.SetValue(key, form[key]); 
    } 
    ... 

然后你parentObject可能是这样的......

 
public partial class ParentObject 
{ 
    IList _FormValues; 

    public void SetValue(string key, string value) 
    { 
     //try and find if this value already exists 
     FormValue v = _FormValues.SingleOrDefault(k => k.Key == key); 

     //if it does just set it 
     if (v != null) 
     { 
      v.Value = value; 
      return; 
     } 

     //else this might be a new form field added and therefore create a new value 
     v = new FormValue 
     { 
      ParentObjectID = this.ID, 
      Key = key, 
      Value = value 
     }; 

     _FormValues.Add(v); 
    } 
} 
12

我在最近的一个项目同样需要。我为此创建了一个类库。我刚刚发布了一个新版本的库。

也许它可以帮助你: ASP.NET MVC Dynamic Forms

+0

只要看看这个相当不错的解决方案的源代码,你是否计划发布2.0版本的源代码?如果不是,你有两个版本之间的差异清单? – Tr1stan 2013-09-20 15:10:28

0

我看不到生成的XForms或任何其他“抽象”在HTML与直线前进一代HTML的比较的巨大的优势“Web窗体2.0”控件列表适用于型号为List<Tuple<Meta, Value>>。注意:在服务器端,您在任何情况下都有义务手动解析结果以适应结构。

搜索“下一层的抽象”是良好的快速发展,但看到“生成代码”(运行时或编译时)都有自己特定的。通常“生成较低层”的生成代码比生成“较高层的抽象层”代码更好。

所以,只要去和在@Foreach循环中生成Web 2控件的wirte代码即可。

4

你可以使用我的FormFactory库做到这一点很容易。

默认情况下,它会反映视图模型以生成PropertyVm[]数组,但您也可以以编程方式创建属性,因此您可以从数据库加载设置,然后创建PropertyVm

这是来自Linqpad脚本的片段。

```

//import-package FormFactory 
//import-package FormFactory.RazorGenerator 


void Main() 
{ 
    var properties = new[]{ 
     new PropertyVm(typeof(string), "username"){ 
      DisplayName = "Username", 
      NotOptional = true, 
     }, 
     new PropertyVm(typeof(string), "password"){ 
      DisplayName = "Password", 
      NotOptional = true, 
      GetCustomAttributes =() => new object[]{ new DataTypeAttribute(DataType.Password) } 
     } 
    }; 
    var html = FormFactory.RazorEngine.PropertyRenderExtension.Render(properties, new FormFactory.RazorEngine.RazorTemplateHtmlHelper()); 

    Util.RawHtml(html.ToEncodedString()).Dump(); //Renders html for a username and password field. 
} 

```

即使世界与可以设置的各种特征的示例的demo site(例如嵌套集合,自动完成,日期选择器等)