AngularJs und ASP.NET MVC – Einführung – Part 4


Ich werde in diesem Beitrag kurz auf den entsprechenden Projektaufbau von ASP.NET MVC mit AngularJs eingehen und dann kurz die Struktur und den Aufbau einer einfachen AngularJs Anwendung beschreiben in Verbindung mit ASP.NET MVC. Ich verwende keine WebAPI zum Abrufen der Daten sondern ganz “normale” JSON Results aus dem Standard Controller.

Im folgenden Beispiel handelt es sich nur um einen Vorschlag wie man sein MVC Projekt aufbauen kann, wenn man zusätzlich noch AngularJs für die Aktualisierung seiner Views verwendet. Ich gehe dabei beim Projektaufbau nicht von einer Single Page Application aus. Denn ich verwende eine “normale” MVC Anwendung, in der ich auch weiterhin das Routing von MVC verwende um mir einen anderen View mit neuen Daten anzuzeigen. Ich verwende AngularJs nur um die Daten eines Views asynchron zu laden und bei Bedarf zu manipulieren. Das wichtigste bei der Strukturierung ist, allerdings, das man selbst mit der Namensgebung und dem Aufbau entsprechend “klar kommt” und auch später noch die Logik dahinter versteht.

Als erstes richtet man sich ein ASP.NET MVC Standard Projekt ein und installiert über NuGet das “Angular JS Core” Paket und das “AngularJS Locale” Paket. (“AngularJS Route” wird in meinem Beispiel nicht benötigt). Das “Angular JS Locale” Paket installieren wir nur, damit später auch die richtigen Trennzeichen bei den Zahlen verwendet werden. Diese beiden JavaScript Pakete “angular.js” und “angular-locale_de-de.js” fügen wir unserem JavaScript Bundle hinzu, welches wir auf jeder Seite laden.

Beispielstruktur für unsere AngularJs Anwendung in ASP.NET MVC.

image

Wir erstellen ein extra JavaScript Verzeichnis “ScriptsApp” in dem wir unsere Custom JavaScript Dateien für dieses Projekt unterbringen. Das Verzeichnis legt man im Root Knoten der Anwendung an und nicht unterhalb des bereits bestehenden “Scripts” Verzeichnisses, damit wir beim Aufklappen des Verzeichnisses auch nur unsere Custom Scripts sehen.

Das gute an AngularJs ist, das es bereits eine gewisse Struktur unterstützt und ich werde in meinem Beispiel die Daten bereits entsprechend der Struktur anlegen.

Als erstes gibt es ein so genanntes “Mainmodul” für jeden View in der man alle Angular Module registriert die man in seiner Anwendung verwenden möchte, mit “angular.module(…)”. In einer One Page App, gibt es hier im Besten Fall nur ein Mainmodule.

Unsere “app.index.js”:

angular.module('app.index', [
               'indexCtrl',
               'homePSrv'
]).run(function($log) {
    $log.log('app.index initialized');
});

Hinweis: Ich schreibe nur in meinen “Mainmodulen” als Prefix “app.”, dann weiß man später besser ob es sich um das jeweilige Hauptmodul für den jeweiligen View handelt. Außerdem heißen die JavaScript Dateinamen immer wie das entsprechend enthaltene Modul wie “app.index” und “app.index.js”. Dann kann man später die Abhängigkeiten leichter zusammenbauen, da man nur die Dateinamen “abschreiben” muss und nicht immer die jeweilige JavaScript Datei öffnen muss.

In unserem Beispiel bedeutet dies, das unsere “app.index” die folgenden Module verwendet “indexCtrl”, und “homePSrv”. Damit die entsprechenden Module gefunden werden können, müssen wir auch die entsprechenden JavaScript Dateien in unserem Index View einbinden.

@section scripts
{
    <script type="text/javascript" src="~/ScriptsApp/views/Home/app.index.js"></script>
    <script type="text/javascript" src="~/ScriptsApp/views/Home/indexCtrl.js"></script>
    <script type="text/javascript" src="~/ScriptsApp/services/homePSrv.js"></script> 
}

Hinweis: Wenn man sich mit ASP.NET MVC auskennt und man dann die Struktur und die Namen der Angular Module sieht, kann  dies durchaus zu Verwirrungen führen. Denn in Angular gibt es so genannte Service Module die aber auf ASP.NET MVC Seite die Daten aus dem Controller z.B. per JSON abrufen. Dann hat Angular aber auch Controller Module, die man auf ASP.NET MVC Seite eher mit dem Model vergleichen kann, auf das man in einem typisierten View zugreift.

Als nächstes definieren wir den Index Controller “indexCtrl” = “indexCtrl.js”

function indexCtrl($log, $scope, homePSrv) {

    //Auf dem Scope ein Personen Objekt anlegen
    $scope.person = {};
    $scope.person.firstname = "Squad";
    $scope.person.lastname = "Wuschel";
    $scope.person.age = 32;
    
    
    $scope.loadData = function() {
        //Vom Controller das die passende Personenliste abrufen
        homePSrv.GetDashboardPersons().then(function (data) {
            $scope.personList = data;
        });
    }

    $scope.listenFilter = "Ütz";
}

angular.module("indexCtrl", ["homePSrv"])
    .controller("indexCtrl", indexCtrl);

Hinweis: Der “$scope” in AngularJs ist wie die “Model” Variable in einem typisierten View in ASP.NET MVC. Denn alles was wir im $scope definieren/ablegen, können wir “direkt” im HTML Markup unserer AngularJs Anwendung abrufen, nur das wir nicht wie in ASP.NET MVC “Model.meineVariable” für den Zugriff schreiben müssen, sondern wir können im Markup direkt auf “meineVariable”, oder in unserem Beispiel auf “person” oder “personList” zugreifen. Der $scope kann per Dipendency Injection auch nicht an einen Service übergeben werden, sondern “nur” an einen Controller.

und unseren Service für den HomeController “homePSrv” = “homePSrv.js”

function homePSrv($http, $log) { 
      this.log = $log, this.http = $http; 
}

homePSrv.prototype.GetDashboardPersons = function () {
    return this.http.get('/Home/GetDashboardPersons').then(function (result) {
        return result.data;
    });
}

angular.module("homePSrv", [])
     //dieser aufruf ist bereits auf minify von Js vorbereitet,
    //denn hier werden die Parameter noch als Stringnamen 
    //in der passenden Reihenfolge mit "übergeben", damit 
    //Angular auch nach dem Minify noch weiß welche Parameter 
    //noch Injected werden sollen
    .service("homePSrv", ['$http', '$log', homePSrv]);

Einzelne Module werden in Angular genau wie unser “MainModul” angelegt, d.h. mit der gleichen Funktion “module” definiert und dann wird ein “controller” angehängt oder bei einem Service, wird ein “service” angehängt und entsprechend initialisiert mit einem Namen und einer zugehörigen Funktion und ihren Parametern.

Alle Parameter die in AngularJs verwendet werden, werden von AngularJs mit der Hilfe von Dipendency Injection (DI) übergeben, d.h. in der Funktion “indexCtrl” wird $log, $scope und homePSrv von Angular per DI übergeben. Wobei es sich bei den Parametern mit “$” um AngularJs interne Parameter/Variablen handelt. Damit Angular weiß welche individuellen Parameter übergeben werden sollen, werden diese hinter dem Modulnamen aufgelistet. In diesem Beispiel wollen wir auf das Modul “homePSrv” zugreifen und geben den entsprechenden Modulnamen an und übergeben dann den Parameter einfach mit in unserer zugehörigen Controller Funktion.

Im Folgenden kurz der ASP.NET MVC Controller den wir über unseren Server “homePSrv” abfragen.

 public class HomeController : Controller
 {
     public ActionResult Index()
     {
         return View();
     }

     public JsonResult GetDashboardPersons()
     {
         List<PersonDashboardModel> models = new List<PersonDashboardModel>();
         models.Add(new PersonDashboardModel() { Age = 22, Price = 3478, Firstname = "Jonas", Lastname = "Müller"});
         models.Add(new PersonDashboardModel() { Age = 42, Price = 5446, Firstname = "Mustava", Lastname = "Ürkütz"});
         models.Add(new PersonDashboardModel() { Age = 26, Price = 12456, Firstname = "Ützgür", Lastname = "Meier"});
         models.Add(new PersonDashboardModel() { Age = 66, Price = 2531, Firstname = "Peter", Lastname = "Neubert"});
         models.Add(new PersonDashboardModel() { Age = 22, Price = 3456, Firstname = "Atze", Lastname = "Schröder"});
         return Json(models, JsonRequestBehavior.AllowGet);
     }
 }

Hinweis: Um die Benennung der Module so einfach wie möglich zu gestalten, heißt bei mir der Modulname genauso wie der zugehörige Controller- oder Servicename die dann über DI aufgerufen werden. Im Folgenden ein Beispiel um genau zu sehen welcher Name wozu benötigt wird.

//MainModule
angular.module("app.meineApp", ["ctrl.meinController", "srv.meinService", "srv.deinService"]);

//Passender Controller
angular.module("ctrl.meinController", ["srv.meinService", "srv.deinService"])
    .controller("CtrlDiName", function ($log, SrvDiName, SrvDiDeinName) {
    $log.log("CtrlDiName wurde initialisiert");
});

//Passende Services
angular.module("srv.meinService", [])
    .service("SrvDiName", function($log) {
        $log.log("SrvDiName wurde initialisiert");
});

angular.module("srv.deinService", [])
    .service("SrvDiDeinName", function ($log) {
        $log.log("SrvDiDeinName wurde initialisiert");
    });

Auf der JavaScript Seite ist alles wichtige definiert, jetzt fehlt nur noch das passende Markup im HTML um unsere AngularJs Anwendung zum Leben zu erwecken.

Dafür gibt es so genannte Angular “directives”, diese fangen immer mit “ng-NameDerDirective” an, z.B.: “ng-app” oder “ng-controller”. Eine Direktive ist im Prinzip nichts anderes wie ein HTML Attribute das mit “ng” beginnt, wenn es sich um eine Standard Angular Direktive handelt. Diese Direktiven werden dann von Angular entsprechend erkannt und es wird die zugeordnete “Funktion/Automatisierung” ausgeführt. Für eigene Direktiven sollte man sich ein eigenes Kürzel ausdenken, um Missverständnisse zu vermeiden.
Dabei ist es wichtig das die “ng-app” Direktive nicht verschachtelt werden kann, denn Sie definiert das Mainmodul der Anwendung und in diesem Fall handelt es sich hier um “app.index” in der alle weiteren Module enthalten sind, die für unsere AngularJs Anwendung benötigt werden.

Unser “Index.cshtml” View sieht fertig, dann folgendermaßen aus.

<div ng-app="app.index" ng-controller="indexCtrl">
    <div class="row">
        <div class="col-md-12">
            <h2>Scope Person anzeigen</h2>
            <p>Vorname: {{ person.firstname }} | Nachname: {{ person.lastname }}</p>
            <p ng-bind="'Alter: ' + person.age"></p>
        </div>
    </div>

    <div class="row">
        <div class="col-md-12">
            <h2>Personen per Ajax abrufen: <button type="button" class="btn btn-default" ng-click="loadData()" >Daten laden</button> </h2>
            <br/>
            <input type="text" class="form-control" ng-model="listenFilter" placeholder="Bitte Filter hier eingeben" />
            <br/>
            <br/>
            <ul ng-repeat="pers in personList | filter:listenFilter">
                <li> {{pers.Firstname | lowercase}}, {{pers.Lastname | uppercase}} -> Alter: {{pers.Age}} und Preis: {{pers.Price | currency}} </li>
            </ul>
        </div>
    </div>
</div>

Nach dem wir die  “ng-app” Direktive festgelegt haben, legen wir den Controller fest der verwendet werden soll mit “ng-controller” und hier verwenden wir den “indexCtrl”. Controller Direktiven können im Gegensatz zur App Direktive verschachtelt werden. Findet AngularJs die aktuelle $scope Variable nicht im aktuellen Controller $scope-Kontext, geht Angular die Controller schritt für schritt zurück und sucht die $scope Variable ebenfalls in allen anderen Controllern bis zum $rootscope.

Hinweis: Der $rootscope ist die Unterste Ebene im $scope, auf die jeder Controller zugriff hat, wenn man also etwas Anwendungsweit zur Verfügung stellen möchte, dann kann man auch den $rootscope per DI übergeben und seine Variable oder Funktion dort definieren. Bzw. in den Minimalbeispielen, die man im Netz findet, in denen kein JavaScript Code sondern nur etwas Markup verwendet wird, hier legt AngularJs selbst alles im $rootscope ab. ACHTUNG bevor man etwas im $rootScope ablegt, sollte man vorher auch übergelgt haben, ob man das ganze nicht auch in einer Factory die als Singleton Instanziert wird ablegen kann. Im $rootScope sollte man so wenig wie möglich ablegen!

Wenn man die App und den Controller definiert hat, kann man auf die $scope bzw. $rootscope Variablen zugreifen. Dafür gibt es wieder unterschiedliche Direktiven, die einfachste ist die “ng-bind” Direktive. Diese Direktive kann über “ng-bind” oder über das Markup “{{scopeVariable}}” die Daten entsprechend darstellen und ist für das einfache Anzeigen von Daten gedacht. In unserem Beispiel zeigen wir hier die Daten vom Person Objekt an.

Hinweis: Das Anzeigen von Modeldaten über die doppelt geschweiften Klammern, sollte man versuchen zu vermeiden und primär ng-bind zum Anzeigen der Daten verwenden und in unserem Fall oben einfach ein span Tag nehmen und hier ng-bind verwenden.

Um die Daten per Ajax nachzuladen, muss erst noch die Funktion “loadData” ausgeführt werden. Dafür bietet Angular die Direktive “ng-click” an, hier können wir einfach die Funktion angeben die bei einem Klick ausgeführt werden soll, in unserem Fall ist es die Funktion “loadData” die wir auf dem $scope definiert haben und diese Funktion lädt dann die entsprechenden Daten aus dem MVC Controller und legt diese auf dem $scope in der Variable “personList” ab.

Listen können mit der Direktive “ng-repeat” durchlaufen werden und man kann auf jedes einzelne Objekt zugreifen und die Daten entsprechend ausgeben. Wir verwenden hier wieder das passende “ng-bind” Markup und zusätzliche Standardausgabefilter für eine formatierte Ausgabe der Werte, wie z.B. “lowercase” oder “currency”. Es ist problemlos möglich auch eigene Filter in AngularJs zu schreiben und dann hier anzugeben.

Außerdem wenden wir einen Standard Filter auf die Liste selbst an um die Daten zu filtern mit “listenFilter”, die Variable wird bereits mit dem Wert “Ütz” vordefiniert und ist an das Eingabefeld mit der Direktive “ng-model” gebunden. Die ng-model Direktive stellt eine Zwei Wege Bindung dar, denn jeden Wert den wir hier eintragen wird auch direkt in unserem Model der zugewiesenen Variable “listenFilter” hinterlegt und direkt auf den Filter der Liste angewendet.

Die Euro Zeichen und die passenden Dezimal- und Tausendertrennzeichen werden nur entsprechend angezeigt, da wir auch die Internationalisierung von Angular mit eingebunden haben – “angular-locale_de-de.js”

image

Das war es dann auch schon, denn so einfach kann es sein Daten mit AngularJS im DOM anzuzeigen. Sollten mir Fehler bei der Erläuterung unterlaufen sein, freue ich mich natürlich über einen entsprechenden Kommentar, denn ich bin aktuell auch noch recht neu in AngularJs.

Das ganze Beispiel kann man auch von meinem Codeplex Account herunterladen oder anschauen.

Advertisements

Ein Gedanke zu „AngularJs und ASP.NET MVC – Einführung – Part 4

  1. Pingback: AngularJS – DropDownListe (select/option) – Part 1 | SquadWuschel's Blog

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s