2010-12-10 14 views
9

In meiner ASP.NET MVC 2-Webanwendung erlaube ich Benutzern, benutzerdefinierte Eingabefelder verschiedener Datentypen zu erstellen, um unser grundlegendes Eingabeformular zu erweitern. Das Erstellen des Eingabeformulars aus einer Sammlung benutzerdefinierter Felder gestaltet sich zwar recht kompliziert, ist aber einfach genug.ASP.NET MVC - Senden eines Formulars mit benutzerdefinierten Feldern unterschiedlicher Datentypen

Allerdings bin ich jetzt an dem Punkt, wo ich mit dem Posting dieses Formulars umgehen und ich bin mir nicht sicher, was der beste Weg, damit umzugehen wäre. Normalerweise würden wir stark typisierte Eingabemodelle verwenden, die an die verschiedenen statisch typisierten Eingaben gebunden werden, die im Formular verfügbar sind. Ich weiß jedoch nicht, wie ich dies mit einer variablen Anzahl von Eingabefeldern machen soll, die verschiedene Datentypen repräsentieren.

Ein repräsentatives Eingabeformular könnte etwa so aussehen:

  • Mein Datumsfeld: [Datum Zeiteingabe control]
  • Mein Textfeld: [Texteingabe field]
  • Meine Datei Feld: [Datei-Upload Steuerung]
  • Mein Nummernfeld: [numerische Eingabesteuerung]
  • Mein Textfeld 2: [Texteingabefeld]
  • etc ...

Ideen dachte ich habe darüber sind:

  • Senden alles als Strings (mit Ausnahme der Datei Eingänge , die speziell behandelt werden müssten).
  • Verwenden Sie ein Modell mit einer "Objekt" -Eigenschaft und versuchen, daran zu binden (wenn das überhaupt möglich ist).
  • Senden einer JSON-Anfrage an meinen Controller mit den Daten richtig codiert und versuchen, das zu analysieren.
  • Manuelle Verarbeitung der Formularsammlung in meiner Controller-Post-Aktion - sicherlich eine Option, aber ich würde das gerne vermeiden.

Hat jemand schon einmal ein Problem wie dieses angegangen? Wenn ja, wie hast du es gelöst?

Update:

Meine „Basis“ bilden alle zusammen auf einem anderen Eingabebereich behandelt wird, so dass eine Lösung braucht nicht diese für jede Art von inheritence Magie zu erklären. Ich bin nur daran interessiert, die benutzerdefinierten Felder auf dieser Schnittstelle zu behandeln, nicht meine "Basis".

Update 2:

Danke zu ARM und smartcaveman; Beide von Ihnen haben eine gute Anleitung gegeben, wie dies getan werden könnte. Ich werde diese Frage mit meiner endgültigen Lösung aktualisieren, sobald sie implementiert wurde.

Antwort

1

Dies ist, wie ich das Problem zu nähern beginnen würde. Ein benutzerdefiniertes Modellbinder wäre ziemlich einfach zu erstellen basierend auf der FormKey-Eigenschaft (die je nach Index und/oder Label bestimmt werden könnte).

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

Auch bemerkte ich einen der Kommentare unten hatte ein gefiltertes Modell Binder-System. Jimmy Bogard von Automapper hat einen sehr hilfreichen Beitrag über diese Methode bei http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx, und später in, http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx überarbeitet. Es war sehr hilfreich für mich beim Erstellen von benutzerdefinierten Modellbindern.

aktualisiert

Ich erkennen, dass ich die Frage falsch verstanden, und dass er speziell Entsendung von Form wurde gefragt, wie „mit einer variablen Anzahl von Eingabefeldern, die unterschiedlichen Datentypen repräsentieren“ zu behandeln. Ich denke, der beste Weg, dies zu tun ist, eine Struktur ähnlich wie oben zu verwenden, aber die Composite Pattern nutzen. Im Grunde müssen Sie eine Schnittstelle wie IFormComponent erstellen und sie für jeden Datentyp implementieren, der dargestellt werden würde. Ich schrieb und kommentierte ein Beispiel Schnittstelle erklären zu helfen, wie dies erreicht werden würde:

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

Ich gehe davon aus, dass die benutzerdefinierten Formulare über eine Art von ID zugegriffen werden können, die als Formparameter enthalten sein können. Mit dieser Annahme könnte der Modellbinder und -anbieter in etwa so aussehen.

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

Offensichtlich gesamte Code ist hier nur den Punkt herüber zu erhalten, und müssten für den tatsächlichen Einsatz abgeschlossen und gereinigt werden. Selbst wenn dies abgeschlossen wäre, würde dies nur an eine Implementierung der IForm-Schnittstelle binden, nicht an ein stark typisiertes Geschäftsobjekt.(Es wäre kein großer Schritt, es in ein Wörterbuch zu konvertieren und einen stark typisierten Proxy mit dem Castle DictionaryAdapter zu erstellen, aber da Ihre Benutzer die Formulare auf der Site dynamisch erstellen, gibt es wahrscheinlich kein stark typisiertes Modell in Ihrer Lösung und das ist irrelevant). Hoffe das hilft mehr.

+0

Danke für die Kommentare, sehr aufschlussreich. – DanP

1

Werfen Sie einen Blick auf, was ich hier getan habe: MVC2 Action to handle multiple models und sehen, ob Sie auf dem richtigen Weg bekommen können.

Wenn Sie eine FormCollection als einen Ihrer Parameter für Ihre Aktion verwenden, können Sie durch diese Formularsammlung gehen, um hier oder da nach Daten zu suchen, um diese Werte an die Daten zu binden und die Daten zu speichern. Sie werden höchstwahrscheinlich sowohl die Strategie als auch die Befehlsmuster nutzen müssen, um dies zum Laufen zu bringen.

Viel Glück, zögern Sie Follow-up Fragen zu stellen.

Edit:

Ihre Methode, die die Arbeit tut soll wie folgt aussehen:

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

Danke für diese Info, Ihr Ansatz sieht sehr interessant aus. Ich werde wahrscheinlich am Montag einige Follow-ups für dich haben. – DanP

+0

ARM; Ich wäre glücklich, Ihnen das Kopfgeld zu verleihen, wenn Sie bereit wären, relevantere Implementierungsdetails zu veröffentlichen/zu teilen. – DanP

+0

Der wichtigste Teil davon ist IUIWrapper.CanHandle (Sie möchten lieber Select als SingleOrDefault verwenden, um mehrere Wrapper zu erhalten). Die CanHandle-Methode verwendet eine FormCollection und versucht, ein Auflistungselement abzurufen (var X = collection ["SomeValue"]; gibt X zurück! = Null;), das bestimmt, ob ein bestimmtes Formularsammlungselement vorhanden ist. Sobald Sie Ihre Sammlung von Wrappern haben, wird jeder Wrapper einen Befehl haben, um dieses bestimmte Element in Ihrem Repository zu speichern und dann einfach durch die Sammlung von Befehlen laufen zu lassen, um die Daten in Ihrem Repository zu speichern. Noch einmal, zögern Sie Follow-up zu fragen. – ARM

0

Ich würde denken, eine der besten Möglichkeiten ist ein benutzerdefiniertes Modell Bindemittel zu schaffen, die es möglich macht, habe hinter den Kulissen eigene Logik und immer noch sehr anpassbaren Code hinter sich.

Vielleicht diese Artikel können Sie helfen:

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

Genauer gesagt würde ich wahrscheinlich als Controller Argument eine benutzerdefinierte Klasse mit allen „Basis“ Eigenschaften enthalten. Die Klasse könnte dann beispielsweise ein Wörterbuch enthalten, das den Namen jedes Feldes mit entweder nur einem Objekt oder einer Schnittstelle verbindet, die Sie einmal für jeden Datentyp implementieren, so dass die Daten später einfach verarbeitet werden können.

/Victor

Verwandte Themen