14 October 2008 ASP.NET MVC, MVC; HowTo Robert Muehsig

Im letzten MVC HowTo ging es um die Beweggründe für ASP.NET MVC - heute werfen wir einen ersten Blick auf das MVC Framework. Das ganze werde ich direkt praktisch in meinem "ReadYou" Projekt verwenden (das derzeit aufgrund von Zeitmangel etwas schleift).

Die Projektvorlage:

Wenn man sich die aktuelle ASP.NET MVC Version runtergeladen hat (momentan sind wir bei Preview 5 auf Codeplex) und ein neues Projekt anlegt, sieht man folgendes neue Item:

image

Anmerkung: Wenn man das deutsche Visual Studio benutzt, scheint die Projektvorlage nicht mit aufzutauchen, dazu muss man die Project- und Itemtemplates von 1033 in 1031 (z. B. C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\CSharp\Web\1031 bzw. \1033) kopieren und danach per Kommandozeile "devenv /installvstemplates" aufrufen. - Danke an "GarlandGreene" für den Hinweis.

Nachdem man dies ausgewählt hat kommt dieses Fenster:

image

Es wird angeboten sofort ein Unittest Projekt anzulegen. "Visual Studio Unit Test" (MS Test) ist per Default ausgewählt - später sollen allerdings noch weitere Test Frameworks (NUnit etc.) folgen.

Projektstruktur:

Nachdem die beiden Wizards durchgelaufen sind finden wir ungefähr (ich hab das UnitTest Projekt verschoben und umbenannt) so aus:

image

Aus der Ordnerstruktur ist auch bereits das MVC zu erkennen:

  • Kurzüberblick:
    • Controller: Logik
    • Models: Die eigentlichen Businessdaten
    • Views: Wie werden die Daten angezeigt
    • </ul></ul>

      Views, Controller & Model:

      Im ausgelieferten Stand besitzt das Template 2 Controller, 3 View-Ordner und ein Model-Ordner:

      image

      • Im "Shared" Ordner werden alle Viewelemente gehalten, die für jeden View nützlich sind, wie z.B. die Masterpage oder eine Errorseite falls etwas schief läuft.
      • Jeder Controller besitzt seinen eigenen Ordner ("AccountController" - "Account" / "HomeController" - "Home")
      • Diese "Pfade" können allerdings über Interfaces angepasst werden - ein Beispiel findet man z.B. hier "Partitioning an ASP.NET MVC application into separate "Areas"" oder Rob Conery Version.
      • Im Models Ordner können normale Klassen gespeichert werden - allerdings kann man diesen Ordner auch leer lassen, wenn man für die normalen Klassen bereits eine Klassenbibliothek hat.
      • </ul>

        Ein Blick auf die Seite:

        Um einen ersten Blick auf die Funktionsweise des MVC Frameworks zu erhaschen ist die Startanwendung (welche bereits mit dem Membershipsystem, Masterpages etc.) ausgeliefert wird, ein guter Anfangspunkt:

        image

        image

        Der Request Flow:

        Justin Etheredge hat auf seinem Blog gut dargestellt, wie der Request einer MVC Anwendung verarbeitet wird und wo es überall die Möglichkeit gibt, selber einzugreifen: ASP.NET MVC Request Flow

        Kommunikation zwischen Controller und View: Vom Controller zum View

        image

         

        Der Controller wird durch den Request Flow (siehe oben) aufgerufen - ich werde später noch eigene Controller in einem seperaten HowTo erstellen. Dabei kann man das ViewData Dictionary nutzen:

        public ActionResult Index()
                {
                    ViewData["Title"] = "Home Page";
                    ViewData["Message"] = "Welcome to ASP.NET MVC!";
        
                    return View();
                }

        Durch den Aufruf der "View()"-Methode wird der View namens "Index" (weil die ControllerAction "Index" heisst) im "Home" Ordner aufgerufen (weil der Controller "Home" heisst).

        Im View "Index.aspx" ist folgender Quellcode:

        <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="ReadYou.WebApp.Views.Home.Index" %>
        
        <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
            <h2><%= Html.Encode(ViewData["Message"]) %></h2>
            <p>
                To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
            </p>
        </asp:Content>

        Durch das einbinden der Masterpage ("MasterPageFile") wird natürlich auch die Masterpage aufgerufen:

        ...
        <head runat="server">
            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
            <title><%= Html.Encode(ViewData["Title"]) %></title>
        </head>
        ...

        Die Daten in dem "ViewData" werden sowohl in der Masterpage als auch in dem eigentlichen View angezeigt - ähnlich wie bei klassischem ASP.

        Stark Typisierte Variante

        Da das Dictionary nicht besonderes typisiert ist, kann man natürlich die ViewData auch noch streng typisiert hinterlegen. Dafür muss man in der Codebehinde Datei der ViewPage, welche im Ausgangszustand so aussieht:

        namespace ReadYou.WebApp.Views.Home
        {
            public partial class Index : ViewPage
            {
            }
        }
        

        Einen eigenen ViewData Typ hinterlegen:

        Index.aspx.cs:

        namespace ReadYou.WebApp.Views.Home
        {
            public class IndexViewData
            {
                public string Text { get; set; }
            }
        
            public partial class Index : ViewPage<IndexViewData>
            {
            }
        }
        

        Index.aspx:

        <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="ReadYou.WebApp.Views.Home.Index" %>
        
        <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
            <h2><%= Html.Encode(ViewData.Model.Text) %></h2>
            <p>
                To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
            </p>
        </asp:Content>

        HomeController.cs:

                public ActionResult Index()
                {
                    ViewData["Title"] = "Home Page";
                    IndexViewData data = new IndexViewData();
                    data.Text = "Hallo streng typisierter Text";
                    ViewData.Model = data;
                    return View();
                }

        Hinweis: Das ViewData["Title"] wird nach wie vor von der Masterpage ausgewertet. In einer MVC Anwendung müssen alle Daten vom Controller übergeben werden - es existiert momentan (Preview 5) nur eine experimentelle Funktion, welche es erlaubt, dass der View sich Daten aus anderen Quellen holt - allerdings entspricht dies nicht der MVC Norm!

        Mehr Informationen findet man in dem Blogpost von Scott Guthrie (damals Preview 3 - Konzept ist gleich geblieben, manche Sachen sind allerdings geändert wurden):
        ASP.NET MVC Framework (Part 3): Passing ViewData from Controllers to Views

         

        Kommunikation zwischen Controller und View: Vom View zum Controller

        image 

        Der View kann über normale Links oder HTML Formulare die Daten an einen Controller schicken. Ein Beispiel ist bereits in dem Template eingebaut: Der Login-Mechanismus - dafür schauen wir uns die Login.aspx an:

        Variante 1: Per GET oder der "ActionLink"

        Für alle die noch nicht angemeldet sind, gibt es auf der Loginseite einen kleinen Link zum Registrieren:

        image

        Im Code:

        <p>
                Please enter your username and password below. If you don't have an account,
                please <%= Html.ActionLink("register", "Register") %>.
            </p>

        Dieser Html Helper generiert den Link damit die Daten beim Klicken zum "AccountController" (der View befindet sich im "Account" Ordner) zur "Register" ActionMethode kommen.

        Variante 2: Per POST

        Der Username / Password wird in einem normalen Html Forumlar eingegeben:

            <form method="post" action="<%= Html.AttributeEncode(Url.Action("Login")) %>">
                <div>
                    <table>
                        <tr>
                            <td>Username:</td>
                            <td><%= Html.TextBox("username") %></td>
                        </tr>
                        <tr>
                            <td>Password:</td>
                            <td><%= Html.Password("password") %></td>
                        </tr>
                        <tr>
                            <td></td>
                            <td><input type="checkbox" name="rememberMe" value="true" /> Remember me?</td>
                        </tr>
                        <tr>
                            <td></td>
                            <td><input type="submit" value="Login" /></td>
                        </tr>
                    </table>
                </div>
            </form>

        Die action-URL wird wieder über einen Html Helper generiert. Wenn der Nutzer das Formular abschickt, kommen die Daten in der "Login" ActionMethode des "AccountControllers" an:

                public ActionResult Login(string username, string password, bool? rememberMe)
                {
        
                    ViewData["Title"] = "Login";
        
                    // Non-POST requests should just display the Login form 
                    if (Request.HttpMethod != "POST")
                    {
                        return View();
                    }
        
                    // Basic parameter validation
                    List<string> errors = new List<string>();
        
                    if (String.IsNullOrEmpty(username))
                    {
                        errors.Add("You must specify a username.");
                    }
        ...
                }

        Es gibt im MVC Framework einen eingebauten Mechanismus, der Formularwerte direkt auf Methoden-Parameter mappen kann (dies ist (natürlich) auch anpassbar). Dies ist nützlich, damit man die Methode besser mit UnitTests testen kann und kein Request Objekt sich erzeugen muss, sondern nur die Parameter entsprechend befüllen.

        In der Methode werden Validierungen etc. vorgenommen. Man kann hier auch auf das normale Request Objekt zugreifen - allerdings sollte man um eine hohe Testbarkeit zu erreichen nicht direkt auf die Request Parameter zugreifen, sondern versuchen das Parametermapping zu verwenden. Stephen Walther hat dazu ein netten Blogpost darüber geschrieben.

        Die Html Helper können auch "streng typisierte" sein und somit braucht man nicht unbedingt mit Strings Arbeiten (bei dem Actionlink z.B. direkt den Methodenaufruf über eine Expression anstatt "Register" als String zu schreiben) - Scott Hanselman hat einen Screencast dazu gemacht.

        "Best Practices", Tipps und Anlaufstellen

        Da das MVC Framework noch nicht mal Beta ist, gibt es natürlich noch keine "Best Practices" - allerdings gibt es bereits mehrere interessante Projekte, welche mit MVC erstellt werden und Tipps geben:


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de