【问题标题】:ASP.NET MVC - Posting a form with custom fields of different data typesASP.NET MVC - 发布具有不同数据类型的自定义字段的表单
【发布时间】:2011-05-23 16:10:31
【问题描述】:

在我的 ASP.NET MVC 2 Web 应用程序中,我允许用户创建不同数据类型的自定义输入字段来扩展我们的基本输入表单。虽然很棘手,但从一组自定义字段构建输入表单已经足够简单了。

但是,我现在已经到了要处理此表单的发布的地步,但我不确定处理此表单的最佳方法是什么。通常,我们会使用从表单上可用的各种静态类型输入绑定的强类型输入模型。但是,我不知道如何使用代表不同数据类型的可变数量的输入字段来执行此操作。

有代表性的输入表单可能类似于:

  • 我的日期字段:[日期时间输入 控制]
  • 我的文本字段:[ 文本输入 领域]
  • 我的文件字段:[文件上传 控制]
  • 我的号码栏:[数字输入控件]
  • 我的文本字段 2:[文本输入字段]
  • 等等……

我想过的想法是:

  • 以字符串形式发送所有内容(文件输入除外,需要特别处理)。
  • 使用具有“对象”属性的模型并尝试绑定到该属性(如果可能的话)。
  • 向我的控制器发送一个 json 请求,其中包含正确编码的数据并尝试对其进行解析。
  • 在我的控制器发布操作中手动处理表单集合 - 当然是一种选择,但我希望避免这种情况。

以前有没有人解决过这样的问题?如果有,你是怎么解决的?

更新:

我的“基本”表单在另一个输入区域一起处理,因此解决方案不需要为此考虑任何类型的继承魔法。我只对处理此界面上的自定义字段感兴趣,而不是我的“基本”字段。

更新 2:

感谢 ARM 和 smartcaveman;你们俩都为如何做到这一点提供了很好的指导。一旦实施,我将用我的最终解决方案更新这个问题。

【问题讨论】:

    标签: asp.net asp.net-mvc viewmodel


    【解决方案1】:

    这就是我开始处理这个问题的方式。根据 FormKey 属性(可以由索引和/或标签确定,具体取决于),自定义模型绑定器将非常容易构建。

    public class CustomFormModel
    {
        public string FormId { get; set; }
        public string Label { get; set; }
        public CustomFieldModel[] Fields { get; set; }
    }
    public class CustomFieldModel
    {
        public DataType DateType { get; set; } //  System.ComponentModel.DataAnnotations
        public string FormKey { get; set; }
        public string Label { get; set; }
        public object Value { get; set; }
    }
    public class CustomFieldModel<T> : CustomFieldModel
    {
        public new T Value { get; set; }
    }
    

    另外,我注意到下面的其中一个 cmets 有一个过滤模型活页夹系统。来自 Automapper 的 Jimmy Bogard 在 http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx 上发表了一篇关于此方法的非常有用的帖子,后来在 http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx 中进行了修改。它对我构建自定义模型绑定器非常有帮助。

    更新

    我意识到我误解了这个问题,他专门询问如何处理“具有代表不同数据类型的可变数量的输入字段”的表单的发布。我认为最好的方法是使用与上面类似的结构,但利用Composite Pattern。基本上,您将需要创建一个类似IFormComponent 的接口,并为要表示的每个数据类型实现它。我编写并评论了一个示例界面,以帮助解释如何实现这一点:

    public interface IFormComponent
    {
        //  the id on the html form field.  In the case of a composite Id, that doesn't have a corresponding 
        //  field you should still use something consistent, since it will be helpful for model binding
        //  (For example, a CompositeDateField appearing as the third field in the form should have an id 
        //  something like "frmId_3_date" and its child fields would be "frmId_3_date_day", "frmId_3_date_month",
        //  and "frmId_3_date_year".
        string FieldId { get; }
    
        //  the human readable field label
        string Label { get; }
    
        //  some functionality may require knowledge of the 
        //  Parent component.  For example, a DayField with a value of "30"
        //  would need to ask its Parent, a CompositeDateField
        //  for its MonthField's value in order to validate
        //  that the month is not "February"
        IFormComponent Parent { get; }
    
        //  Gets any child components or null if the 
        //  component is a leaf component (has no children).
        IList<IFormComponent> GetChildren();
    
        //  For leaf components, this method should accept the AttemptedValue from the value provider
        //  during Model Binding, and create the appropriate value.  
        //  For composites, the input should be delimited in someway, and this method should parse the 
        //  string to create the child components.  
        void BindTo(string value);
    
        //  This method should parse the Children or Underlying value to the 
        //  default used by your business models.  (e.g. a CompositeDateField would 
        //  return a DateTime.  You can get type safety by creating a FormComponent<TValue>
        //  which would help to avoid issues in binding.
        object GetValue();
    
        //  This method would render the field to the http response stream.
        //  This makes it easy to render the forms simply by looping through 
        //  the array.  Implementations could extend this for using an injected 
        //  formatting 
        void Render(TextWriter writer);
    } 
    

    我假设可以通过某种可以作为表单参数包含的 id 访问自定义表单。有了这个假设,模型绑定器和提供者可能看起来像这样。

    public interface IForm : IFormComponent
    {
        Guid FormId { get; }
        void Add(IFormComponent component);
    }
    public interface IFormRepository
    {
        IForm GetForm(Guid id);
    }
    public class CustomFormModelBinder : IModelBinder   
    {
        private readonly IFormRepository _repository;
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            ValueProviderResult result;
            if(bindingContext.ValueProvider.TryGetValue("_customFormId", out result))
            {
                var form = _repository.GetForm(new Guid(result.AttemptedValue));
                var fields = form.GetChildren();
                //  loop through the fields and bind their values 
                return form;
            }
            throw new Exception("Form ID not found.");
        }
    }
    

    显然,这里的所有代码都只是为了说明问题,需要完成并清理以供实际使用。此外,即使完成,这也只会绑定到 IForm 接口的实现,而不是强类型业务对象。 (将其转换为字典并使用 Castle DictionaryAdapter 构建强类型代理并不是一个巨大的步骤,但由于您的用户正在网站上动态创建表单,因此您的解决方案中可能没有强类型模型,并且这无关紧要)。希望这有助于更多。

    【讨论】:

    • 感谢 cmets,非常有见地。
    【解决方案2】:

    看看我在这里做了什么:MVC2 Action to handle multiple models,看看是否能让你走上正轨。

    如果您使用 FormCollection 作为操作的参数之一,则可以通过该表单集合在此处或此处查找数据位,以便将这些值绑定到任何内容,然后保存数据。您很可能需要同时利用策略和命令模式才能使其发挥作用。

    祝你好运,请随时提出后续问题。

    编辑:

    你的方法应该是这样的:

    private/public void SaveCustomFields(var formId, FormCollection collection) //var as I don't know what type you are using to Id the form.
    {
        var binders = this.binders.select(b => b.CanHandle(collection)); //I used IOC to get my list of IBinder objects
        // Method 1:    
        binders.ForEach(b => b.Save(formId, collection)); //This is the execution implementation.
        // Method 2:
        var commands = binders.Select(b => b.Command(formId, collection));
        commands.ForEach(c => c.Execute());    
    }
    
    public DateBinder : IBinder //Example binder
    {
        public bool CanHandle(FormCollection collection)
        {
            return (null != collection["MyDateField"]); //Whatever the name of this field is.
        }
    
        //Method 1
        public void Save(var formId, FormCollection collection)
        {
            var value = DateTime.Parse(collection["MyDateField"]);
            this.someLogic.Save(formId, value); //Save the value with the formId, or however you wish to save it.
        }
        //Method 2
        public Command Command(var formId, FormCollection collection)
        {
            //I haven't done command pattern before so I'm not sure exactly what to do here.
            //Sorry that I can't help further than that.
        }
    }
    

    【讨论】:

    • 感谢您提供此信息,您的方法看起来很有趣。我可能会在星期一为您提供一些后续服务。
    • ARM;如果您愿意发布/分享更多相关的实施细节,我很乐意奖励您。
    • 其中最重要的部分是 IUIWrapper.CanHandle(您将希望使用 Select 而不是 SingleOrDefault 来获取多个包装器)。 CanHandle 方法接受一个 FormCollection 并尝试获取一个集合元素 (var X = collection["SomeValue"]; return X != null;),这将确定是否存在特定的表单集合元素。一旦你有你的包装器集合,每个包装器都会有一个命令来保存那个特定的元素到你的存储库,然后简单地运行命令集合来将数据存储到你的存储库。再次,随时询问后续行动。
    • @ARM:感谢您更新您的帖子;我在想,在我的场景中,我必须在每个字段的集合中依赖多个逻辑表单条目。最重要的是,我需要知道我的自定义字段的 id 和数据类型,并根据数据类型,尝试将第三个感兴趣的条目转换为适当的类型。
    • 只要您知道表单名称和类型,您就可以为该表单名称创建一个活页夹。否则,您可以创建一些隐藏的表单值(“MyNumberField”与“MyNumberField_id”和“MyNumberField_type”),然后使用它们进行绑定。然而,它们都会以字符串的形式出现,如何将字符串“int”转换为 int 类,然后将“MyNumberField”绑定到该类是一个我幸好不必解决的问题。
    【解决方案3】:

    我认为最好的选择之一是创建一个自定义模型绑定器,这使得在后台拥有自定义逻辑和仍然非常可自定义的代码成为可能。

    也许这些文章可以帮助你:

    http://www.gregshackles.com/2010/03/templated-helpers-and-custom-model-binders-in-asp-net-mvc-2/

    http://www.singingeels.com/Articles/Model_Binders_in_ASPNET_MVC.aspx

    更具体地说,我可能会将包含所有“基本”属性的自定义类作为控制器参数。例如,该类可以包含一个字典,该字典将每个字段的名称链接到一个对象或一个接口,您为每种数据类型实现一次,以便以后处理数据变得简单。

    /维克多

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-13
      • 1970-01-01
      • 1970-01-01
      • 2019-01-11
      • 1970-01-01
      • 2019-04-16
      相关资源
      最近更新 更多