BlackCoin's Corner

In diesem Blog dreht es sich zu 90 % um den Themenbereich C# .Net

März 2011 - Einträge

Wie übergebe ich ein Array von POCO’s an den Controller in ASP .NET MVC 2

 

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.

tableSample

 

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.

Fehler

 

Um diesem Fehler auf die Spur zu kommen, könnte man sich z.B. die übergebenen Daten anschauen.uebergebeneDaten

 

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

fertig

Visual Studio 2010 Service Pack 1

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.

Posted: Mrz 08 2011, 07:04 von Lars Schmitt | mit no comments
Abgelegt unter:
verschiedene Pattern zur Objekt Instanziierung

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.