Thomas Bandt

Über mich | Kontakt | Archiv

ASP.NET MVC - Gruppierte ListBox (und täglich grüßt das Murmeltier)

Ich habe eine Liste von Usern, die ich in einer ListBox auswählbar machen möchte. Diese User sind allesamt jeweils einer Firma zugeordnet. Also hatte ich die fromme Idee, sie entsprechend ihrer Firma in der ListBox zu gruppieren. Ein Standard-Feature, welches seit Urzeiten in HTML enthalten ist. Und in ASP.NET MVC? Tja ... nix da. Genau wie schon bei WebForms, muss man sich selbst helfen.

Aber halb so wild, ist ja alles schön leicht erweiterbar und wir sind ja nicht auf den Kopf gefallen :-). Nachfolgend also ein kleines Beispiel dafür, wie man das Problem lösen kann bzw. könnte - denn ich habe mir in diesem Fall aus Zeitgründen verkniffen, einen generischen Helper zu bauen.

Das View-Model

   1:  public class ProjectViewModel
   2:  {
   3:   
   4:      public ProjectViewModel()
   5:      {
   6:          CompaniesWithEligibleUsers = new Dictionary<Company, List<User>>();
   7:      }
   8:   
   9:      public Dictionary<Company, List<User>> CompaniesWithEligibleUsers { get; set; }
  10:      public List<Guid> SelectedUsers { get; set; }
  11:   
  12:  }

Der Controller

   1:  public ActionResult CreateProject()
   2:  {
   3:      var model = new ProjectViewModel();
   4:   
   5:      // => TEST
   6:      model.SelectedUsers = new List<Guid>();
   7:      model.SelectedUsers.Add(new Guid("2EEAA0EF-F346-4DF2-B2BE-4CC29343E6F4")); // Danny
   8:      model.SelectedUsers.Add(new Guid("6755B9F0-07AF-48B3-9F68-63A2F4A74402")); // Patricia
   9:   
  10:      var companies = CompanyService.GetCompaniesByClient(CurrentClient.ID);
  11:      foreach (var company in companies)
  12:      {
  13:          model.CompaniesWithEligibleUsers.Add(company, UserService.GetUsersByCompany(company.ID));
  14:      }
  15:   
  16:      return View(model);
  17:  }

Der View

   1:  <fieldset>
   2:      <legend></legend>
   3:      <%= Html.LabelFor(m => m.SelectedUsers, "Zugriff für")%>
   4:      <%= Html.GroupedUsersListBox("SelectedUsers", Model.SelectedUsers, Model.CompaniesWithEligibleUsers)%>
   5:  </fieldset>

Die Erweiterung

   1:  public static string GroupedUsersListBox<TModel>(this HtmlHelper<TModel> htmlHelper, string name, 
   2:      List<Guid> selection, Dictionary<Company, List<User>> source)
   3:  {
   4:   
   5:      var html = new StringBuilder();
   6:      html.AppendLine(string.Format("<select name=\"{0}\" id=\"{0}\" multiple=\"multiple\">", name));
   7:   
   8:      foreach (var group in source)
   9:      {
  10:          html.AppendLine(string.Format("<optgroup label=\"{0}\">", group.Key.Name));
  11:          foreach (var user in group.Value)
  12:          {
  13:              var selectedText = selection.Any(u => u == user.ID) ? " selected=\"selected\"" : string.Empty;
  14:              html.AppendLine(string.Format("<option value=\"{0}\" label=\"{1}\"{2}>{1}</option>", user.ID, user.FullName, selectedText));
  15:          }
  16:          html.AppendLine(string.Format("</optgroup>"));
  17:      }
  18:   
  19:      html.AppendLine("</select>");
  20:   
  21:      return html.ToString();
  22:   
  23:  }

Das Ergebnis

Kommentare

  1. Robert Mühsig schrieb am Montag, 17. Mai 2010 10:15:00 Uhr:

    Ich find es immer etwas unleserlich, wenn man das HTML Markup im Code zusammenbaut. Das versuch ich bei größeren "Controls" eher in ein Partialview auszulagern.
    Ist dieser Code überhaupt testgetrieben entwickelt worden? ;)
    Würdest du in einem "größeren" Projekt sowas wie diese Extensionmethod mit Unittests checken?
  2. Thomas schrieb am Montag, 17. Mai 2010 10:20:00 Uhr:

    Wie, warum, was ... hä? :) Warum PartialView? Dann hast du den ganzen Käse dort drin, mit vielen schönen gelben <% und %> - wo siehst du da den Vorteil?

    Man könnte auch noch jedes Tag generieren lassen. Vielleicht liegt's an meinem Background, aber wegen 3 HTML-Tags ist mir der Overhead zu groß - und so sehe ich gleich, was Phase ist.

    Test-getrieben entwickle ich sowas nicht, es sei denn F5 im Browser gehört dazu ;)
  3. Robert Mühsig schrieb am Montag, 17. Mai 2010 10:34:00 Uhr:

    Warum ein PartialView? Find ich etwas übersichtlicher.
    Ich hab z.B. ein View in dem mehrmals solch eine Struktur generieren muss.
    [div class="xxx"]
    [a class="yyy" href="#"]... [/a]
    [/div]
    Das Teil hab ich als .ascx + ViewModel (z.B. Linktitel & xxx wird nur generiert wenn ein Flag gesetzt ist).
    In meiner Extensionmethod bau ich mir nur das Viewmodel zusammen und gebe das generierte HTML Markup dann via RenderPartial zurück. Irgendwie find ich es (momentan) übersichtlicher so ;)
    PS: Dein Blog mag keine Spitzen klammern und es kommt die gelbe Seite des ASP.NET Todes ;)
  4. Thomas schrieb am Montag, 17. Mai 2010 10:43:00 Uhr:

    PartialViews setze ich dann ein, wenn ich einen größeren Block an HTML-Code wiederholt einsetzen möchte. In dem Fall rendere ich aber nur ein einzelnes HTML-Control, genau wie Html.TextBox() auch. Da brauch ich kein PartialView.

    Und ViewModel in der ExtensionMethod zusammenbauen? Puh ... wäre das nicht Aufgabe des Controllers? :)
  5. Robert Mühsig schrieb am Montag, 17. Mai 2010 10:49:00 Uhr:

    Puh... jein ;) Der Controller gibt z.B. ein größeres Viewmodel zurück mit mehreren Listenelementen. Pro Listenelement will ich wiederum ein "PartialView" nutzen, welches aber unter Umständen eine etwas andere Struktur des Viewmodels verlangt. Im Prinzip wandelt die Extensionmethod das Viewmodel nur um. Logik im Sinne von "Daten holen" passiert da nicht.
  6. Thomas schrieb am Montag, 17. Mai 2010 11:03:00 Uhr:

    Grenzwertig ... aber genehmigt ;)

    Führen halt viele Wege nach Rom.


« Zurück  |  Weiter »