ASP.NET MVC mit AngularJS und UI Router


Damit aus einer AngularJS Anwendung auch eine “richtige” SPA (Single Page Application) wird, benötigt man noch eine passende Router Erweiterung, die sich um die Navigation kümmert und den richtigen Inhalt für die jeweilige URL/Status lädt. Bei der Wahl des passenden Routers gibt es zum einen ngRouter der direkt vom AngularJS Team entwickelt wird oder UI-Router welcher von der Community stammt und auch nested views (verschachtelte Seiten) unterstützt. Ich habe mich in meinen Projekten für den UI-Router entschieden, aufgrund des besseren Funktionsumfangs und da ich wie beim ASP.NET MVC auch weiterhin nested views verwenden möchte, die ngRouter nicht out of the Box unterstüzt.

In den meisten Beispielen die ich bisher gesehen habe, die AngularJS mit ASP.NET MVC und einem Router einsetzen, wurde ASP.NET MVC nur noch verwendet um die AJAX Calls für die Daten bereitzustellen. Alle Views wurden meist in einfache HTML Dateien ausgelagert und man verzichtet damit eigentlich völlig auf die Annehmlichkeiten die einem mit Razor Views zur Verfügung stehen.

Daher verwende ich in meinen Projekten weiterhin Razor Views (cshtml) um z.B. per Authorize Attribut zu bestimmen welche Views nicht ausgeliefert werden, wenn der Benutzer nicht die entsprechenden Rechte besitzt. Oder ich passe die Views bereits entsprechend der aktuellen Benutzerrechte an bevor ich diese an den Client ausliefere.

Damit man AngularJS mit UI-Router und Razor Syntax inklusive nested views auch verwenden kann, muss man die folgenden Einstellungen im MVC Projekt vornehmen.
(Wenn das ganze auch einfacher geht, bin ich gern für einen Tipp dankbar.)

  • Erstellen eines neuen Views mit Beliebigen Namen z.B. “Index.cshtml” in einem beliebigen Controller, der nur den folgenden Inhalt hat:
<div data-ui-view></div>
  • Als Standardroute unseren neuen View eintragen. (In meinem Fall ist es “Index” im “HomeController”)
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • Für alle restlichen Views bis auf _Layout.cshtml und Index.cshtml wird für “Layout = null” eingestellt. (Das bedeutet für diese Views wird nur das HTML generiert das auch im View enthalten ist. Denn normalerweise wird der Inhalt dieser Seite in der “_Layout.cshtml” Datei an der Stelle wo “@RenderBody” steht von MVC eingefügt und der HTML Code der gesamten Seite zurückgegeben, dies wird durch das Setzten von “null” verhindert.)
@{
    Layout = null;
}

<h2>AddPerson</h2>

...
  • Wenn man nested views verwendet, in denen “@RenderBody” verwendet wird, dann in den nested views das “@RenderBody” entfernen und durch “<div ui-view></div>” ersetzten. (Bei mir findet man dies z.B. in den Untermenüs.)
  • Auch für die nested views wird jetzt im Controller eine passende ActionResult Funktion benötigt, damit der HTML Code abgerufen werden kann. (Vorher wurde dies durch “@RenderBody” von “allein” durch MVC geregelt und es war für nested views keine extra Controllerfunktion notwendig)
//Der neue Index View
 public ActionResult Index()
 {
     return View();
 }

//Der Nested View für das Menü 
 public ActionResult Submenue()
 {
     return View("_HomeSubmenue");
 }
  • Am Ende sollte es nur noch ein “@RenderBody” Element im “_Layout.cshtml” geben, hier wird automatisch unsere “Index.cshtml” Datei durch MVC eingesetzt und ab da übernimmt dann später UI-Router, um den richtigen Inhalte anzuzeigen.

Damit sollten alle ASP.NET MVC Einstellungen abgeschlossen sein. Jetzt kann man sich ganz um das Definieren der passenden Routen bzw. Status im Angular/JavaScript kümmern und die passenden “ui-sref” Links für die Navigation erstellen. Dafür muss natürlich AngularJS und UI-Router installiert und eingebunden sein.

Die App Datei mit den States (Routen) für mein Beispiel sieht dann z.B. folgendermaßen aus:

angular.module("app.main", [
        "ui.router",
        "mainCtrl",
       ... 
])
    .config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
        //http://scotch.io/tutorials/javascript/angular-routing-using-ui-router
        $urlRouterProvider.otherwise("/Home/HighCharts");

        $stateProvider
            //Home Controller
            .state("Home", {
                url: "/Home",
                templateUrl: "Home/Submenue"
            })
            .state("Home.HighCharts", {
                url: "/HighCharts",
                templateUrl: "Home/HighCharts"
            })
            .state("Home.Formvalidation", {
                url: "/Formvalidation",
                templateUrl: "Home/Formvalidation"
            })
            .state("Home.ModelBinderOhneWatch", {
                url: "/ModelBinderOhneWatch",
                templateUrl: "Home/ModelBinderOhneWatch"
            })
            .state("Home.DirectiveTests", {
                url: "/DirectiveTests",
                templateUrl: "Home/DirectiveTests"
            })
            .state("Home.AngularStrapModal", {
                url: "/AngularStrapModal",
                templateUrl: "Home/AngularStrapModal"
            })
            .state("Home.TemplateUrlTest", {
                url: "/TemplateUrlTest",
                templateUrl: "Home/TemplateUrlTest"
            })
            //Account Controller
            .state("Account", {
                url: "/Account",
                templateUrl: "Account/AccountSubmenue"
            })
            .state("Account.PersonsList", {
                url: "/Persons",
                templateUrl: "Account/PersonsList"
            })
            .state("Account.ManageRights", {
                url: "/Rights/:personId?Name",
                templateUrl: "Account/ManageRights"
            })
            .state("Account.AddPerson", {
                url: "/Add",
                templateUrl: "Account/AddPerson"
            });
    }
    ]);

image

Im config Part werden die passenden States für unsere Anwendung festgelegt. Wie genau sich die States zusammensetzten und wie das ganze aufgebaut wird, dazu gibt es einige sehr gute Beispiele im Netz.

Die zugehörige “_Layout.cshtml” sieht mit UI-Router Navigation “ui-sref” dann folgendermaßen aus:

<!DOCTYPE html>
<html lang="en" ng-app="app.main" ng-controller="mainCtrl as main">
<head>
    <meta charset="utf-8" />
    <base href="@Url.Content("~")" />
    @Styles.Render("~/Content/css")
</head>
<body>
    <div class="navbar navbar-default navbar-fixed-top">
        <div class="container">
            <div class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                    <li ng-class="{'active': main.MenueHomeActive()}">
                        <a ui-sref="Home.HighCharts"><strong>Home</strong></a> 
                    </li>
                    <li ng-class="{'active': main.MenueAccountActive()}">
                        <a ui-sref="Account.PersonsList"><strong>Account</strong></a>
                    </li>
                </ul>
            </div>
        </div>
    </div>

    <div class="container" id="mainContainer">
        @RenderBody()
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/Angular/App")
</body>
</html>

Ein passenden Submenü mit nested view Element “ui-view” und der Subnavigation. Mit Hilfe von “ui-sref-active” wird die CSS Klasse “active” gesetzt, wenn der View angezeigt wird, der in “ui-sref” angegeben wurde.

@{
    Layout = null;
}

<div class="row">
    <div class="col-lg-12">
        <div class="btn-group" role="group">
            <a ui-sref=".PersonsList" ui-sref-active="active" class="btn btn-default">
                <strong>Personen Liste</strong>
            </a>
            <a ui-sref=".AddPerson" ui-sref-active="active" class="btn btn-default">
                <strong>Person erstellen</strong>
            </a>
        </div>
    </div>
</div>

<div ui-view></div>

Das komplette Beispiel findet man wie immer bei mir in CodePlex.

Advertisements

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