2010-12-10 12 views
9

W mojej aplikacji sieci web ASP.NET MVC 2, pozwalam użytkownikom tworzyć niestandardowe pola wejściowe różnych typów danych w celu rozszerzenia naszego podstawowego formularza wejściowego. Choć trudne, budowanie formularza wejściowego z kolekcji niestandardowych pól jest wystarczająco proste.ASP.NET MVC - Publikowanie formularza z niestandardowymi polami różnych typów danych

Jednak jestem teraz w punkcie, w którym chcę obsłużyć wysłanie tego formularza i nie jestem pewien, jaki byłby najlepszy sposób na obsłużenie tego formularza. Normalnie użylibyśmy mocno wpisanych modeli wejściowych, które byłyby związane z różnymi statycznie wpisanymi danymi wejściowymi dostępnymi w formularzu. Jednak nie mam pojęcia, jak to zrobić ze zmienną liczbą pól wejściowych, które reprezentują różne typy danych.

Przedstawiciel forma wejście może wyglądać:

  • Moja data pola: [data i godzina wejścia sterowania]
  • moim polu tekst: [tekst wejściowy pola]
  • moim pliku pole: [przesyłanie pliku sterowanie]
  • Moje pole numeryczne: [sterowanie wprowadzaniem numerycznym]
  • Moje pole tekstowe 2: [pole wprowadzania tekstu]
  • etc ...

Pomysły Myślałem o to:

  • Wysyłanie wszystko jako ciągi (z wyjątkiem wejść plików , które należy szczególnie traktować).
  • Używanie modelu z właściwością "obiekt" i próba powiązania z tym (jeśli jest to możliwe).
  • Wysyłanie żądania json do kontrolera z poprawnie zakodowanymi danymi i próba przeanalizowania tego.
  • Ręczne przetwarzanie kolekcji formularzy w moim kontrolerze po akcji - z pewnością opcja, ale chciałbym tego uniknąć.

Czy ktoś wcześniej poradził sobie z takim problemem? Jeśli tak, jak to rozwiązałeś?

Aktualizacja:

Mój „baza” forma jest obsługiwana na innym obszarze wejściowym wszyscy razem, więc to rozwiązanie nie musi stanowić jakiegokolwiek dziedziczenia magii do tego. Interesuje mnie tylko obsługa pól niestandardowych w tym interfejsie, a nie moich "podstawowych".

Aktualizacja 2:

Dziękuję ARM i smartcaveman; oboje zapewniliście dobre wskazówki, jak można to zrobić. Zaktualizuję to pytanie moim ostatecznym rozwiązaniem po jego wdrożeniu.

Odpowiedz

1

W ten sposób zacznę podejść do problemu. Niestandardowy segregator modelu można byłoby łatwo zbudować na podstawie właściwości FormKey (która może być określona przez indeks i/lub etykietę w zależności od).

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

Zauważyłem również, że jeden z poniższych komentarzy miał filtrowany system segregatorów. Jimmy Bogard z Automapper zrobił bardzo pomocny post na temat tej metody pod adresem http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/03/17/a-better-model-binder.aspx, a następnie poprawił ją w wersji http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/11/19/a-better-model-binder-addendum.aspx. Bardzo mi pomogło w tworzeniu niestandardowych segregatorów modeli.

Aktualizacja

zdałem sobie sprawę, że błędnie zinterpretował pytanie, i że został specjalnie pytając, jak radzić delegowania postaci „ze zmienną liczbą pól wejściowych, które reprezentują różne typy danych.” Myślę, że najlepszym sposobem na to jest użycie struktury podobnej do powyższej, ale skorzystaj z Composite Pattern. Zasadniczo, będziesz musiał stworzyć interfejs taki jak IFormComponent i zaimplementować go dla każdego typu danych, który byłby reprezentowany. Pisałem i skomentował interfejs przykład pomóc wyjaśnić, w jaki sposób to osiągnąć:

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

jestem przy założeniu, że można uzyskać poprzez niestandardowe formy jakiegoś id, które mogą być zawarte jako parametr formularza. Przy takim założeniu model spoiwa i dostawca mogą wyglądać mniej więcej tak.

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

Oczywiście, cały kod jest tu po prostu, aby uzyskać punkt całej, i musiałyby zostać zakończone i oczyścić do faktycznego wykorzystania. Ponadto, nawet jeśli jest to zakończone, wiązałoby się tylko z implementacją interfejsu IForm, a nie z silnie wpisanym obiektem biznesowym.(Nie byłoby wielkim krokiem do przekonwertowania go do słownika i zbudowania silnie typowanego proxy przy użyciu Castle DictionaryAdapter, ale ponieważ Twoi użytkownicy dynamicznie tworzą formularze w witrynie, prawdopodobnie nie ma silnie wpisanego modelu w twoim rozwiązaniu i to nie ma znaczenia). Mam nadzieję, że to pomaga bardziej.

+0

Dzięki za komentarze, bardzo wnikliwe. – DanP

1

Zobacz, co zrobiłem: MVC2 Action to handle multiple models i sprawdź, czy uda Ci się dotrzeć na właściwą drogę.

Jeśli używasz FormCollection jako jednego z parametrów do wykonania akcji, możesz przejść przez tę kolekcję formularzy, szukając bitów danych tutaj lub tam, aby powiązać te wartości z dowolnymi danymi, a następnie zapisać dane. Najprawdopodobniej będziesz musiał wykorzystać zarówno strategię, jak i wzorce poleceń, aby to zadziałało.

Powodzenia, proszę zadawać pytania uzupełniające.

Edit:

Twoja metoda, która działa powinien wyglądać mniej więcej tak:

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

Dzięki za te informacje Twoje podejście wygląda bardzo interesująco. Prawdopodobnie będę miał dla ciebie kilka następnych w poniedziałek. – DanP

+0

ARM; Byłbym szczęśliwy mogąc przyznać ci nagrodę, jeśli chciałbyś opublikować/udostępnić bardziej istotne szczegóły dotyczące implementacji. – DanP

+0

Najważniejszą częścią tego jest IUIWrapper.CanHandle (aby uzyskać wiele wrapperów, należy użyć Select, a nie SingleOrDefault). Metoda CanHandle pobiera FormCollection i próbuje pobrać element kolekcji (var X = collection ["SomeValue"]; return X! = Null;), który określa, czy istnieje określony element kolekcji formularzy. Po utworzeniu kolekcji owijaczy każda owijka będzie miała polecenie zapisania tego konkretnego elementu do repozytorium, a następnie po prostu uruchomi zbiór komend do przechowywania danych w repozytorium. Jeszcze raz, proszę o kontakt. – ARM

0

Myślę, jeden z najlepszych sposobów jest stworzenie niestandardowego modelu spoiwa, co sprawia, że ​​możliwe jest mają niestandardową logikę za kulisami i wciąż bardzo konfigurowalny kod.

Może te artykuły mogą pomóc:

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

Dokładniej to pewnie przyjąć jako argument kontrolera niestandardowej klasy ze wszystkimi „bazy” Właściwości włączone. Klasa mogłaby na przykład zawierać słownik łączący nazwę każdego pola z samym obiektem lub interfejsem, który implementujesz raz dla każdego typu danych, ułatwiając późniejsze przetworzenie danych.

/Victor

Powiązane problemy