ASP.NET MVC 中的动态(运行时生成)表单

发布于 2024-07-21 08:11:47 字数 1435 浏览 4 评论 0 原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

如日中天 2024-07-28 08:11:47

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

也许它可以帮助你:
ASP.NET MVC 动态表单

I had the same need in a recent project. I created a class library for this. I just released a new version of the library.

Maybe it can help you:
ASP.NET MVC Dynamic Forms

踏雪无痕 2024-07-28 08:11:47

您可以使用我的 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.
}

```

有一个演示站点,其中包含您可以设置的各种功能的示例(例如嵌套集合) 、自动完成、日期选择器等)

You can do this very easily using my FormFactory library.

By default it reflects against a view model to produce a PropertyVm[] array, but you can also create the properties programatically, so you could load settings from a database then create PropertyVm.

This is a snippet from a Linqpad script.

```

//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.
}

```

Theres a demo site with examples of the various features you can set up (e.g. nested collections, autocomplete, datepickers etc.)

疧_╮線 2024-07-28 08:11:47

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

//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

当您提交运行时生成的视图(来自 ApplicationFields)时,只需循环您的 FormCollection 并尝试将其设置在您需要更新的 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);
    }
}

Another option is to have a very loosely coupled database schema.

//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

When you submit your runtime generated View (from ApplicationFields) then just loop through your FormCollection and try and set it on the ParentObject you need to update.

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]);
    }
    ...

Then your parentObject might be something like this...

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);
    }
}
终遇你 2024-07-28 08:11:47

实现此目的的一种方法是创建您自己的 ModelBinder,它将成为生成表单的核心。 modelbinder 负责验证 ModelState 并重建类型化的 ViewDataModel(假设您的视图是类型化的)。

DataAnnotations 模型绑定器可能是此自定义的一个很好的参考modelbinder 允许您通过 ViewDataModel 上的 Attributes 描述属性的验证(并提示 UI 渲染)。 然而,这都是定义的编译时间,但对于开始编写自定义模型绑定器来说是一个很好的参考。

在您的情况下,您的模型绑定器应该在运行时从 xml 文件/字符串获取字段的验证。

如果您有这样的路线:

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

那么您可以在 FormsController.Index(string formName) 中找到正确的表单 xml 并将其传递给视图。

FormsModel 应该包含所有可能的方法来获取数据,我没有看到任何其他方法可以解决这个问题。 Xml 可以映射到一个函数名称(甚至可能是参数),您可以使用 FormsModel 上的反射调用该函数名称来填充 ViewData 或键入的 ViewDataModel与数据。

表单索引视图可以通过采用 XmlDocumentHtmlHelper 扩展从该 xml 生成表单。

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

这是一项艰巨的任务,但在我看来,成功完成后非常值得:)

更新模型数据的更好替代方案是非常松散的数据库架构,正如 David Liddle 所建议的那样。 我仍然会遇到将其保存为 xml(或其他序列化格式)并使用它来生成视图并保存自定义 ModelBinder 的验证规则的麻烦,以便您对布局有更多的控制以及每个字段的验证。

One way to do this is to create your own ModelBinder which would be at the heart of your generated forms. A modelbinder is responsible for validating the ModelState and rebuilding the typed ViewDataModel (assuming your views are typed).

The DataAnnotations model binder could be a good reference for this what this custom modelbinder allows you to do is via Attributes on your ViewDataModel describe the attribute's validation (and hint at UI rendering). However this is all defined compile time but would be a great reference to start off writing a custom modelbinder.

In your case your model binder should get the validation for a field at runtime from an xml file/string.

If you have a route like:

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

Then you could locate the correct form xml in FormsController.Index(string formName) and pass it to the view.

The FormsModel should hold all the possible methods to get data I dont see any other way around this. The Xml could map to a function name (possibly even arguments) that you can invoke using reflection on the FormsModel to fill the ViewData or typed ViewDataModel with data.

The view for Form Index could generate a form from that xml through an HtmlHelper Extension that takes an XmlDocument.

Then when you (or asp.net mvc) binds your form to your ViewData your custom model binder is invoked it can inspect the current controller values to look for the formName and look up the corresponding xml that holds all the validation rules. The ModelBinder is then responsible for filling up ModelState with any runtime defined errors.

It's a hard task but when pulled off succesfully well worth it in my view :)

Update a better alternative to model data would be a very loose database schema as David Liddle suggests. I'd still go through the trouble of saving it as xml (or someother serialized format) and using that for generating the view and to hold validation rules for a custom ModelBinder so that you have more control on layout and validation of each field.

把昨日还给我 2024-07-28 08:11:47

科察克的回答非常有吸引力。

至少有两个客户端 XForms 引擎。 这是一个:

https://community.emc.com/community/edn/xmltech

cottsak's answer is very attractive.

There are at least two client-side XForms engines. Here's one:

https://community.emc.com/community/edn/xmltech

白芷 2024-07-28 08:11:47

与使用“Web Forms 2.0”控件列表(例如 List> 模型)直接生成 HTML 相比,我看不出生成 XForms 或任何其他“抽象”相对于 HTML 的巨大优势。 。 注意:在服务器端,无论如何,您都必须手动解析结果以使其适合您的结构。

搜索“下一层抽象”有利于快速开发,但是请注意,“生成代码”(运行时或构建时)有其自己的特定性。 通常,生成“较低层”的代码是比生成“较高抽象层”代码更好的解决方案。

因此,只需在 @Foreach 循环中编写生成 Web 2 控件的代码即可。

I can't see huge advantages of generating XForms or any other "abstraction" over the HTML comparing with straight forward generation of HTML with "Web Forms 2.0" controls list for model like List<Tuple<Meta, Value>>. Note: on server side you in any case would be obligated to parse results manually to fit it to your structrures.

Searching for "next layer abstractions" is good for rapid development, but see, "generate code" (runtime or build-time) has its own specific. Usually the generating code of "lower layer" is the better solution than generating the "higher abstract layer" code.

So just go and wirte code that generate Web 2 Controls in @Foreach loop.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文