Archiv für den Monat Mai 2014

AngularJS – DropDownListe (select/option) – Part 1


Bis vor kurzem, habe ich knockout für das Modelbinding auf JavaScript Ebene verwendet um mein UI dynamisch zu aktualisieren. Dann habe ich mir ein paar Einführen zu AngularJS angeschaut und habe etwas damit rumgespielt. Und bisher kann ich nur sagen das ich begeistert davon bin. Wie für jedes Framework benötigt man auch hier ein wenig Zeit um sich im Framework zurecht zu finden, aber es lohnt sich.

Es gibt eine menge Einführungsbeispiele auf YouTube die man sich anschauen kann, denn die Standarddokumentation von AngularJS ist gewöhnungsbedürftig und bei weitem nicht so gut wie bei knockout.

Daher werde Ich ein paar kleinere Tutorials zu den Folgenden Themen in der nächsten Zeit verfassen:

Alle Beispiele sind mit Hilfe von Visual Studio 2013 und ASP.NET MVC umgesetzt und können bei CodePlex angeschaut bzw. heruntergeladen werden.

Als erstes werde ich mit der DropDownListe mit einer “Bitte wählen” Auswahl starten. Wichtig ist es das man die DropDownListe nicht mit MVC über einen HTML Helper erstellt, sondern wir holen uns die Daten z.B. über einen AJAX Request in unserem Controller ab und wir verwenden kein “ng-repeat” sondern “ng-options” zum füllen der DropDownListe.

<select class="form-control" ng-model="selectedIndex" ng-options="item as item.Text for item in Levels">
    <option value="" ng-hide="selectedIndex">Bitte wählen</option>
</select>

Wichtig ist hier, das “selectedIndex” im scope definiert ist. Denn im “selectedIndex” wird unsere aktuelle Auswahl “abgelegt” von AngularJS und in unserem Beispiel handelt es sich hier immer um ein komplettes “Level” Objekt, welches im “Levels” Array enthalten ist. Wobei das “Levels” Array ebenfalls auf unserem scope definiert sein muss.

Der Controller sieht z.B. folgendermaßen aus:

function DropDownlistCtrl($log, $scope, dropDownlistSrv) {
    var scope = $scope, log = $log;
    $scope.selectedIndex = "";

    $scope.ValueChanger = function () {
        //Im selectedIndex liegt unser Komplettes "Objekt", daher können wir hier auch auf Text und Value zugreifen.
        return "Text: " + scope.selectedIndex.Text + " Value: " + scope.selectedIndex.Value;
    };

    $scope.LoadListData = function () {
        dropDownlistSrv.loadLevelsData().then(function (data) {
            scope.Levels = data;
        });
    };

    $scope.ResetListData = function () {
        scope.selectedIndex = "";
    }
}

angular.module("app.dropDownlistCtrl", ["app.dropDownlistSrv"])
    .controller("dropDownlistCtrl", DropDownlistCtrl);

Damit aber auch das “Bitte wählen” angezeigt wird, ist es wichtig das der “value” von “option” auf einen leeren String gestellt wird und es ist zu empfehlen das Sie “ng-hide” verwenden, dann verschwindet “Bitte wählen” aus der Auswahl sobald Sie ein Item ausgewählt haben. Wenn Sie den “selectedIndex” später wieder zurücksetzten, dann wird auch “Bitte wählen” wieder angezeigt.

//.NET Controller Code zum Zurückgeben des "Levels" wird als JSON Result zurückgegeben
public JsonResult GetLevels()
{
    List<ListItem> items = new List<ListItem>();
    items.Add(new ListItem() { Text = "Home", Value = "5"});
    items.Add(new ListItem() { Text = "Live", Value = "6"});
    items.Add(new ListItem() { Text = "Dev", Value = "7"});
    items.Add(new ListItem() { Text = "Staging", Value = "8"});

    return Json(items, JsonRequestBehavior.AllowGet);
}

Ebenfalls wichtig zu wissen ist, das AngularJS in der DropDownListe in “value” den Index des Array Einträgt, welches angezeigt wird und NICHT den “richtigen value”, denn der aktuell ausgewählte Value steht in meinem Beispiel immer in “selectedIndex”. D.h. aber auch, man kann nicht einfach mit einem einfachen Submit Button die ausgewählten Daten an den Server senden, denn hier würde nur der ausgewählte Index ankommen. Hier sollte man dann auch mit AngularJS die entsprechenden Daten an den Server zurücksenden.

Durch den Wert der in “ng-options” steht, wird angezeigt was genau “passieren” soll:

“for item in Levels” –> Gibt an das wir das “Levels” Array auf unserem scope durchgehen und hier kann dann in jedem Schleifendurchlauf auf den aktuellen Wert mit “item” zugegriffen werden.

“item as item.Text” –> Das erste “item” gibt an welcher Wert im “ng-model” für eine aktuelle Auswahl abgelegt werden kann, in unserem Fall wird das komplette Objekt dort abgelegt. Es hätte aber auch “item.Text” dort stehen können, dann würden wir im “selectedIndex” nur den Text ablegen. Der zweite Wert “item.Text” gibt an das in der “option” der “Text” angezeigt werden soll.

Das komplette Beispiel findet Ihr dann bei CodePlex.

ASP.NET MVC und Dependency Injection für Views – Part 3


Einen View mit Dependency Injection (Di) zu initialisieren ist möglich, sollte aber nicht prinzipiell verwendet werden, sondern nur wenn z.B. ein Lokalisierungsservice direkt Strings auf dem View ausgeben und übersetzten soll. Denn im View soll normalerweise keine Logik untergebracht werden, sondern nur zur reinen Anzeige der Modeldaten dienen.

Um einen View mit Hilfe von Di zu initialisieren sind die folgenden Schritte notwendig.

Überschreiben der Standardklasse für die ViewCreation “WebViewPage”, dafür müssen wir unsere eigene Klasse anlegen die von “WebViewPage” ableitet.

 public abstract class CustomDiWebViewPage<TModel> : WebViewPage<TModel>
    {
        /// <summary>
        /// Lokalisation Interface, welches per Property Injektion vom DiFramework initialisiert wird.
        /// </summary>
        public ILocalizeable Localizeable { get; set; }
    }

Achtung wichtig ist hier das “abstract”. Das Interface und die passende Implementierung stellen wir zur Verfügung und könnte z.B. folgendermaßen aussehen:

 public interface ILocalizeable
 {
        /// <summary>
        /// den passend Lokalisierten String zurückgeben
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        string GetLocalizedString(string name);
 }

 /// <summary>
 /// Interface und Implementierung nur zu Demonstrationszwecken im gleichen Projekt!
 /// </summary>
 public class Localizeable : ILocalizeable
 {
     /// <summary>
     /// den passend Lokalisierten String zurückgeben
     /// </summary>
    public string GetLocalizedString(string name)
     {
         //TODO Lokalisierung entsprechend einbinden.
        return name + " localized";
     }
 }

Damit MVC auch weiß welche Klasse für das “anzeigen” des Views zuständig ist, gibt es hier zwei Möglichkeiten:

1. In der web.config, die sich im Views Verzeichnis befindet die Zeile mit “WebViewPage” durch unsere Benutzerdefinierte ViewPage ersetzen.

 <!--<pages pageBaseType="System.Web.Mvc.WebViewPage">-->
 <pages pageBaseType="User.Web.UI.Helper.CastleWindsor.CustomDiWebViewPage">
   <namespaces>
     <add namespace="System.Web.Mvc" />
     <add namespace="System.Web.Mvc.Ajax" />
     <add namespace="System.Web.Mvc.Html" />
     <add namespace="System.Web.Optimization"/>
     <add namespace="System.Web.Routing" />
     <add namespace="User.Web.UI" />
     <add namespace="User.Global.Resources" />
     <add namespace="User.Web.Common.WebAttributes" />
   </namespaces>
 </pages>

Jetzt wird bei jedem Rendern einer Seite unsere “CustomDiWebViewPage” verwendet.

2. Im View selbst nicht @model verwenden sondern über @inherits den View angeben über den die Seite gerendert werden soll z.B.:

@inherits User.Web.UI.Helper.CastleWindsor.CustomDiWebViewPage<User.Web.Models.Account.LoginModel>

 

Im View selbst kann dann einfach auf die Lokalisierung zugegriffen werden

@Localizeable.GetLocalizedString(„Test Lokalisierung“)

So jetzt zum wichtigsten Schritt, die Dependency Injection. Leider ist es mit Castle Windsor und einer CustomControllerFactory (älterer Post von mir über Di und Castle Windsor) nicht möglich einen View wie oben beschrieben zu Injecten.

Out of the Box wird das ganze aber z.B. von ninject unterstützt und mit Hilfe des NuGet Packages:

https://www.nuget.org/packages/Ninject.MVC3

muss für die Dependency Injection für einen View nur noch unsere Implementierung dem Interface zugewiesen werden im ninject DiKernel und alles weitere Funktioniert durch die Implementierung von IDependencyResolver von ninject. Wenn ich noch ermitteln kann, wie das ganze auch mit Castle Windsor funktioniert, werde ich den Post entsprechend anpassen.

Quellen:

http://bradwilson.typepad.com/blog/2010/07/service-location-pt3-views.html

http://sachabarbs.wordpress.com/2011/02/05/mvc-dependency-injection-into-views/

http://weblogs.asp.net/scottgu/archive/2010/10/19/asp-net-mvc-3-new-model-directive-support-in-razor.aspx