ASP.NET MVC - 發布具有不同數據類型的自定義欄位的表單
在我的 ASP.NET MVC 2 Web 應用程序中,我允許使用者創建不同數據類型的自定義輸入欄位來擴展我們的基本輸入表單。雖然很棘手,但從一組自定義欄位中建構輸入表單已經足夠簡單了。
但是,我現在已經到了要處理此表單的發布的地步,我不確定處理此表單的最佳方法是什麼。通常,我們會使用從表單上可用的各種靜態類型輸入綁定的強類型輸入模型。但是,我不知道如何使用代表不同數據類型的可變數量的輸入欄位來執行此操作。
一個有代表性的輸入表單可能類似於:
- 我的日期欄位:$$ date time input control $$
- 我的文本欄位:$$ text input field $$
- 我的文件欄位:$$ file upload control $$
- 我的號碼欄位:$$ numerical input control $$
- 我的文本欄位 2:$$ text input field $$
- ETC…
我想過的想法是:
- 將所有內容作為字元串發送(文件輸入除外,需要特別處理)。
- 使用具有“對象”屬性的模型並嘗試綁定到該屬性(如果可能的話)。
- 使用正確編碼的數據向我的控制器發送 json 請求並嘗試解析它。
- 在我的控制器發布操作中手動處理表單集合 - 當然是一個選項,但我很想避免這種情況。
以前有沒有人解決過這樣的問題?如果是這樣,您是如何解決的?
更新:
我的“基本”表單在另一個輸入區域一起處理,因此解決方案不需要為此考慮任何類型的繼承魔法。我只對處理此界面上的自定義欄位感興趣,而不是我的“基本”欄位。
更新 2:
感謝 ARM 和 smartcaveman;你們倆都為如何做到這一點提供了很好的指導。一旦實施,我將用我的最終解決方案更新這個問題。
這就是我開始處理這個問題的方式。基於 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; } }另外,我注意到下面的評論之一有一個過濾模型綁定係統。來自 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 建構強類型代理並不是一個巨大的步驟,但由於您的使用者正在網站上動態創建表單,因此您的解決方案中可能沒有強類型模型,並且這無關緊要)。希望這有助於更多。