Neulich hatte ich während der Implementations- Phase eines ASP.Net MVC 2 Projektes, ein kleines Problem.
Ich wollte eine HTML Tabelle, in welche man mit Hilfe dieser Tabelle auch neue Daten hinzufügen kann und diese im Anschluss zu speichern.

Das Dynamische hinzufügen, war mittels JQuery selbstverständlich sehr schnell realisiert.
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<MvcApplication10.Models.Kunde>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Startseite
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% if (Model.Count > 0) %>
<% { %>
<table id="templateTableRow" style="display:none">
<tr>
<td><%: Html.TextBox("Name")%></td>
<td><%: Html.TextBox("Vorname")%></td>
<td><%: Html.TextBox("Strasse")%></td>
<td><%: Html.TextBox("Hausnummer")%></td>
</tr>
</table>
<%using (Html.BeginForm()) %>
<% { %>
<table id="tbl">
<tr>
<th><%: Html.LabelFor(m => Model[0].Name) %></th>
<th><%: Html.LabelFor(m => Model[0].Vorname) %></th>
<th><%: Html.LabelFor(m => Model[0].Strasse) %></th>
<th><%: Html.LabelFor(m => Model[0].Hausnummer) %></th>
</tr>
<% foreach(var item in Model) %>
<% { %>
<tr>
<td><%: item.Name %></td>
<td><%: item.Vorname %></td>
<td><%: item.Strasse %></td>
<td><%: item.Hausnummer%></td>
</tr>
<% } %>
</table>
<input type="submit" value="save" />
<% } %>
<% } %>
<input type="button" value="Add" onclick="BLOCKED SCRIPT$('#tbl').append($('#templateTableRow').html())" />
</asp:Content>
public ActionResult Index()
{
List<Kunde> kunden = new List<Kunde>();
kunden.Add(new Kunde { Name = "Schmitt", Vorname="Lars", Strasse="...", Hausnummer="..."});
kunden.Add(new Kunde { Name = "...", Vorname="Walter", Strasse="...", Hausnummer="..."});
return View(kunden);
}
Fein, das war ja einfach, jedoch der Schrecken folgt auf dem Fuße, denn nachdem ein paar neue Testdaten erfasst und das Formular abgeschickt wurde.
Gelangten die eingegebenen Daten, nicht in die dafür vorgesehene Generischen Liste vom Typ Kunde.

Um diesem Fehler auf die Spur zu kommen, könnte man sich z.B. die übergebenen Daten anschauen.
Warum wird mit dieser Daten das Model nicht gefüllt?
Hintergrund:
Die Daten die von einer Webseite, an die Controller Methode übergeben werden, werden erst durch einen so genannten ModelBinder, den einzelnen Parameter zugewiesen.
Zwar hat MS uns einen Default ModelBinder spendiert, jedoch kann dieser logischerweise auch nicht alle Fälle abdecken.
Da man jetzt gesehen hat, wie die Daten übergeben werden, könnte man auf die Idee kommen, ein neues Model zu erstellen welches die übergebenen Daten aufnehmen kann.
public class Kunde
{
public string[] Name { get; set; }
public string[] Vorname { get; set; }
public string[] Strasse { get; set; }
public string[] Hausnummer { get; set; }
} Jedoch wäre dieses der Falsche Weg!
Einige Suchanfragen später … Ein eigener ModelBinder muss her.
Um einen eigenen ModelBinder zu erstellen, muss nur das Interface IModelBinder aus dem Namensraum System.Net.MVC Implementiert werden.
public class KundenListModelBinder : IModelBinder
{
#region IModelBinder Member
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
List<Kunde> kunden = new List<Kunde>();
NameValueCollection col = controllerContext.HttpContext.Request.Form;
var namen = col["Name"].Split(',');
var vornamen = col["Vorname"].Split(',');
var strassen = col["Strasse"].Split(',');
var hausnummern = col["Hausnummer"].Split(',');
Kunde kunde = null;
for (int i = 0; i < kunden.Count; i++)
{
kunde = new Kunde();
kunde.Name = namen<img src="http://dotnet-forum.de/emoticons/emotion-55.gif" alt="Idea" />;
kunde.Vorname = vornamen<img src="http://dotnet-forum.de/emoticons/emotion-55.gif" alt="Idea" />;
kunde.Strasse = strassen<img src="http://dotnet-forum.de/emoticons/emotion-55.gif" alt="Idea" />;
kunde.Hausnummer = hausnummern<img src="http://dotnet-forum.de/emoticons/emotion-55.gif" alt="Idea" />;
kunden.Add(kunde);
}
return kunden;
}
#endregion
}
Um diesen ModelBinder, nun auch benutzen zu können, muss er nur noch dem MVC Framework bekannt gemacht werden.
Wie so oft, gibt es nicht nur einen Weg dieses zu bewerkstelligen.
1. Die Globale Methode
Der ModelBinder kann, innerhalb der Global.asax dem Framework bekanntgemacht werden.
protected void Application_Start()
{
ModelBinders.Binders[typeof(List<Kunde>)] = new KundenListModelBinder();
}
2. Die Lokale Methode (von mir bevorzugt)
Der Modelbinder kann aber auch immer nur da, wo er auch wirklich benötigt wird angegeben werden.
public ActionResult Index([ModelBinder(typeof(KundenListModelBinder))] List<Kunde> kunden)
{
return View(kunden);
}
abschließend natürlich der Beweis, dass es auch wirklich klappt

Im Laufe des heutigen Tages, (update seid 19:00 MEZ) steht für alle MSDN Abonnenten, das erste Service Pack,
für die von uns so geliebte Entwicklungsumgebung zur Verfügung.
Link Download: MSDN
Was wurde geändert: Änderungen
ab dem 10 März sollten dann auch, alle anderen das Update durchführen können.
Viele von euch werden, an dieser Stelle diesmal nichts neues Lernen, doch leider musste ich erst heute wieder feststellen, dass selbst solche Grundlagen nicht jedem bekannt sind.
Im großen und ganzen, existieren 3 unterschiedliche Möglichkeiten ein Objekt zu Instanziieren. Wenn man diese verschiedenen Pattern mit bedacht einsetzt, kann eine Anwendung einiges an Geschwindigkeit zulegen. Denn machen wir uns nichts vor, egal wie gut die Anwendung ist, ein Benutzer wird manche Funktionen im Lebenszyklus eines Programmes nur sehr selten benutzen.
Anhand von einigen kurzen sehr stark vereinfachten Beispielen, möchte ich euch heute diese 3 Möglichkeiten vorstellen.
- Lazy Loading (träges Laden)
- Eager Loding (eifriges Laden)
- Over-Eager Loading (über eifriges Laden)
Stellen wir uns vor, wir haben eine Anwendung mit einigen Objekten. Wenn wir jetzt alle Objekte beim Starten der Anwendung Instanziieren würden, dann würde der Startvorgang der Anwendung im Verhältnis gesehen, einfach viel zu lange dauern. Genau an dieser Stelle könnte uns dieses Pattern helfen.
Lazy Loading:
Ein Objekt wird nicht im Cache vorgehalten und erst zu dem Zeitpunkt wo dieses Objekt benötigt wird wird es Instanziiert.
public class Orders
{
public int OrderID { get; set; }
private List<Detail> _orderDetails;
public List<Detail> OrderDetails
{
get
{
return _orderDetails ?? LoadDetailsForOrderID(orderID: OrderID);
}
}
//irgendwo im Programm dann der Zugriff auf die Details
public void GetOrder(int orderID)
{
Orders order = GetOrder(id: orderID);
//erst jetzt werden die OrderDetails initialisiert
order.OrderDetails.ForEach( ... );
}
} oder auch mittels des neuen Objektes Lazy<T> (.Net Framework 4)
public class Orders
{
public int OrderID { get; set; }
public Lazy<List<Detail>> OrderDetails {get;set;}
//irgendwo im Programm dann der Zugriff auf die Details
public void GetOrder(int orderID)
{
Orders order = GetOrder(id: orderID);
//erst jetzt werden die OrderDetails initialisiert
order.OrderDetails.Value.ForEach((item) => item.ToString());
}
}
Verständlicherweise ergeben sich in unserer Anwendung, auch immer wieder verschiedene Situationen wo man als Entwickler sagen kann, wenn ein User dieses Objekt benutzt, wird er mit hoher Wahrscheinlichkeit auch ein anderes weiteres Objekt benötigen. Also können wir das schon einmal mittels der Eager Loading Instanziieren. Ich bekomme also ein Vollständiges Objekt und kann damit sofort Arbeiten.
Eager Loding:
public class Orders
{
public Orders(int orderID)
{
_orderDetails = LoadDetailsForOrderID(orderID: orderID);
}
public int OrderID { get; set; }
private List<Detail> _orderDetails;
public List<Detail> OrderDetails
{
get
{
return _orderDetails;
}
}
//irgendwo im Programm dann der Zugriff auf die Details
public void GetOrder(int orderID)
{
Orders order = GetOrder(id: orderID);
//das Object ist schon lange vorhanden, denn die Instanziierung wurde im Konstruktor vorgenommen
order.OrderDetails.ForEach( ... );
}
}
Over-Eager Loding:
Im Prinzip kann man dieses Pattern als eine Mischung aus den anderen beiden Sehen. Es wird erst einmal das Grundobjekt erzeugt, und zurückgegeben. Der Anwender kann also sofort mit der Order Arbeiten, sollte er sich irgendwann entschließen auf die Details zuzugreifen bekommt er diese, zumindest schon mal schneller als beim Eager Loading bestenfalls natürlich sofort.
Wie Funktioniert das ganze:
Das Geheimnis liegt in der Implementation, es wird zb durch die Instanziierung ein Hintergrundprozess angestoßen, der die OrderDetails beschafft.
public class Orders
{
public Orders(int orderID)
{
Task.Factory.StartNew(() =>
{
_orderDetails = LoadDetailsForOrderID(orderID: orderID);
}
}
public int OrderID { get; set; }
private List<Detail> _orderDetails;
public List<Detail> OrderDetails
{
get
{
return _orderDetails;
}
}
//irgendwo im Programm dann der Zugriff auf die Details
public void GetOrder(int orderID)
{
Orders order = GetOrder(id: orderID);
//ob das Object schon vorhanden ist, weiß man nicht
order.OrderDetails.ForEach( ... );
}
} So sollte das natürlich nicht sein, denn sobald Threads oder Tasks mit ins Spiel kommen, darf man natürlich nicht auf ein anständiges Locking verzichten.
Fazit: Abschließend kann ich euch leider keine Empfehlung geben wie Ihr es am besten machen könnt, das hängt leider immer von der jeweiligen Domäne ab ich kann euch nur raten, nutzt und Experimentiert mit diesen Möglichkeiten, um für euch die passende Vorgehensweise zu entdecken.