2010-12-10 51 views
9

在我的ASP.NET MVC 2 Web應用程序中,我允許用戶創建不同數據類型的自定義輸入字段來擴展我們的基本輸入表單。雖然棘手,但從一組自定義字段構建輸入表單足夠簡單。ASP.NET MVC - 發佈不同數據類型的自定義字段的表單

但是,我現在要處理這個表單的發佈,我不確定處理這個問題的最佳方法是什麼。通常情況下,我們會使用強類型的輸入模型,它們可以從表單上的各種靜態類型輸入中進行綁定。然而,我不知道如何用代表不同數據類型的可變數量的輸入字段。

代表性的輸入形式可能類似於:

  • 我的日期字段:[日期時間輸入 控制]
  • 我的文本字段:文本輸入 場]
  • 我的文件字段:[文件上傳 控制]
  • 我的號碼欄:[數字輸入控制]
  • 我的文本字段2:文本輸入字段]
  • 等...

想法我也想過是:

  • 發送的一切作爲字符串(除了文件輸入,這需要專門處理)。
  • 使用具有「對象」屬性的模型並嘗試綁定到該模型(如果這甚至可能)。
  • 發送一個json請求到我的控制器與正確編碼的數據並試圖解析它。
  • 手動處理表單集合在我的控制器後操作 - 當然是一種選擇,但我很樂意避免這種情況。

有沒有人處理過這樣的問題?如果是這樣,你是如何解決它的?

更新:

我的「基地」的形式在另一個輸入區域處理一起,這樣的解決方案不需要考慮任何形式的傳承魔法這一點。我只是想處理這個界面上的自定義字段,而不是我的「基礎」字段。

更新2:

謝謝ARM和smartcaveman;你們兩個都爲如何做到這一點提供了很好的指導。一旦實施後,我會用我的最終解決方案更新這個問題。

回答

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; } 
} 

此外,我注意到下面的評論之一有一個過濾模型活頁夾系統。 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構建強類型代理並不是一個巨大的步驟,但由於您的用戶正在網站上動態創建表單,因此您的解決方案中可能沒有強類型模型,這是無關緊要的)。希望這有助於更多。

+0

感謝您的評論,非常有見地。 – DanP 2010-12-17 10:44:05

1

看看我在這裏所做的: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. 
    } 
} 
+0

感謝您的信息,您的方法看起來非常有趣。週一我可能會爲你做一些跟進。 – DanP 2010-12-11 00:10:05

+0

ARM;如果您願意發佈/分享更多相關的實施細節,我很樂意爲您獎勵賞金。 – DanP 2010-12-12 23:51:57

+0

其中最重要的部分是IUIWrapper.CanHandle(您將希望使用Select而不是SingleOrDefault來獲取多個包裝)。 CanHandle方法接受一個FormCollection並嘗試獲取一個集合元素(var X = collection [「SomeValue」]; return X!= null;),它將確定是否存在特定的表單集合元素。 一旦你有你的收集包裝,每個包裝將有一個命令來保存該特定的元素到您的存儲庫,然後只需運行通過收集的命令將數據存儲到您的存儲庫。 再次,請隨時詢問後續行動。 – ARM 2010-12-13 16:51:33

0

我認爲的最好的選擇之一是創建一個自定義的模型綁定,這使得它能夠在後臺擁有自定義邏輯,並且仍然有非常可定製的代碼。

也許這些文章可以幫助您:

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

更具體地說,我可能會採取作爲控制器參數的自定義類的所有「基地」屬性包括在內。然後這個類可以包含一個字典,將每個字段的名稱鏈接到一個對象或一個接口上,每個數據類型實現一次,以便稍後處理數據。

/Victor

相關問題