MVVM dla aplikacji Windows Store pisanych w JavaScript

Sporo się mówi/pisze o zastosowaniu wzorca MVVM w aplikacjach Windows Store i Windows Phone (teraz to chyba jest pod jedną nazwą ;)). Istnieje kilka bibliotek, jak na przykład MVVMLight, które pomagają w implementacji tego wzorca w XAML-u i C#. Niewiele się jednak mówi o aplikacjach Windows Store pisanych w języku JavaScript. Wydaje mi się, że niewiele osób kiedykolwiek próbowało stworzyć aplikacje dla Windows Phone w tym języku. W zasadzie to się nie dziwię, JavaScript to dziwny język i można go kochać lub nienawidzić. Niemniej jednak chciałbym pokazać, że pisanie aplikacji Windows Store w JS nie jest takie złe a w tym poście przede wszystkim chciałbym pokazać, że zastosowanie wzorca MVVM jest możliwe i jest bardzo proste.

1. Zacznijmy od stworzenia nowego projektu, polecam przy tworzeniu wybrać Navigation App, ponieważ stworzy nam się prawie pusta aplikacja, ale ze wszystkimi potrzebnymi rzeczami. Jeśli zaczęlibyśmy od Blank App, musielibyśmy posiedzieć jeszcze trochę nad poukładaniem stron, tak, aby można było zacząć programować.

winjs1

2. Język XAML jest wręcz stworzony do stosowania wzorca MVVM, wbudowany w ten język mechanizm bindingu pozwala nam stosować MVVM bez potrzeby dodawania jakiekolwiek zewnętrznej biblioteki. HTML niestety nie jest taki wspaniały, dlatego musimy wesprzeć się dodatkową biblioteką. Istnieje kilka bibliotek, których możemy użyć, ja natomiast najpewniej czuje się z biblioteką knokcoutjs, dlatego to w niej pokażę przykład. Bibliotekę tą możemy dodać za pomocą Nugeta, wyszukujemy knockoutjs i klikamy Install. Po tej operacji w katalogu Scripts powinny się nam pojawić dwa pliki knockout-{nr.wersji}.js i knockout-{nr.wersji}.debug.js.

winjs2

 

3. Otwieramy plik default.html i dodajemy link do pliku knockout-x.x.x.js, najlepiej przeciągając plik z solution explorera, w odpowiednie miejsce w dokumencie, ewentualnie wpisując ręcznie:

 <script src="Scripts/knockout-3.2.0.js"></script>

4. Tworzymy sobie nowy katalog o nazwie viewmodels. W tym katalogu będziemy przechowywać nasze klasy ViewModel. Dodajemy pierwszy plik js o nazwie MainViewModel.js. W środku, zaimplementujemy bardzo prostą logikę, wraz z dwoma polami do odczytu oraz metodą wywołującą MessageBox.

var MainViewModel = function (firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    this.sayHello = function () {
        var msg = new Windows.UI.Popups.MessageDialog("Hi! my name is " + this.firstName + " " + this.lastName);
        msg.showAsync();
    }
}

5. Przechodzimy do pliku home.html, i w miejsce „Content goes here” wklejamy prosty binding imienia i nazwiska oraz binding funkcji sayHello.

Imie:  
Nazwisko:

6. Zostajemy jeszcze w tym samym pliku i w znaczniku head, dodajemy odwołanie do naszego skryptu z MainViewModel, tak samo jak w punkcie 3 przeciągając plik MainViewModel.js w odpowiednie miejsce.
7. Zostaje nam na sam koniec tylko stworzyć obiekt viewmodel i podbindować pod odpowiedni widok. Otwieramy więc plik home.js i w funkcji ready dodajemy dwie linijki:

var vm = new MainViewModel("Jan", "Kowalski");
ko.applyBindings(vm);

8. Efekt, jaki powinniśmy dostać to aplikacja wyświetlająca Jan Kowalski oraz przycisk, po naciśnięciu którego powinien pojawić się MessageBox.

winjs3

 

Jeśli ktoś wcześniej nie miał do czynienia z biblioteką Knockoutjs, to polecam stronę http://knockoutjs.com/ na której znajdziecie tutorial, dzięki któremu w bardzo szybki sposób można nauczyć się podstaw tej biblioteki. Oczywiście można tutaj stosować TwoWay binding, oraz wiele innych ciekawych rzeczy, które odkryjecie na pewno przechodząc tutorial.

Kod źródłowy do pobrania

Interceptor dla WCF-a

Ostatnio programując w javie (sic!) spodobała mi się jedna rzecz, którą chciałem wprowadzić do swoich projektów w .NET, a mianowicie Interceptor. Pozwala on w prosty sposób udekorować metodę własną logiką i to wszystko za pomocą jednej adnotacji. Podobną funkcję spełniają ActionFilter w ASP.NET MVC. Ja natomiast chciałem dodać warstwę logowania do metod WCF-a, więc zacząłem szukać. Niestety nie znalazłem nic co działałoby równie dobrze co Javowe interceptory, więc postanowiłem napisać coś samemu.

Przede wszystkim chciałem aby taki interceptor można było zakładać właśnie za pomocą atrybutu (albo na pojedynczą operację albo na cały serwis). Rezultatem mojej twórczości została taka oto klasa.

public class WCFInterceptor : Attribute, IOperationBehavior, IServiceBehavior
{
    private Type actionType;

    public WCFInterceptor(Type actionType)
    {
        if (actionType == null)
            throw new ArgumentException("Type cannot be null");

        if (!actionType.GetInterfaces().Contains(typeof(IParameterInspector)))
            throw new ArgumentException("Type must implement IParameterInspector");

        this.actionType = actionType;
    }

    #region IOperationBehavior Members

    //...

    void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, 
        DispatchOperation dispatchOperation)
    {
        try
        {
            IParameterInspector actionInstance = (IParameterInspector)Activator.CreateInstance(this.actionType);
            dispatchOperation.ParameterInspectors.Add(actionInstance);
        }
        catch
        {
            throw new ArgumentException(string.Format("Could not create instance of type: {0}", this.actionType.Name));
        }
    }

    #endregion

    #region IServiceBehavior Members

    // ... 

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
        {
            foreach (var operation in endpoint.Contract.Operations)
            {
                operation.Behaviors.Add(this);
            }
        }
    }

    #endregion
}

Jako parametr w konstruktorze przyjmuje on typ klasy, która musi implementować IParameterInspector, jest to prosty interfejs, który wymaga zaimplementowania dwóch metod: AfterCall oraz BeforeCall. Główną przyczyną, dla której chciałem stworzyć taki interceptor było logowanie wejścia i wyjścia z operacji, dlatego dodatkowo stworzyłem klasę LogInterceptorAction, która może zostać przesłana w konstruktorze atrybutu.

public class LogInterceptorAction : IParameterInspector
{
    private readonly ILog log = LogManager.GetLogger(typeof(LogInterceptorAction));

    public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
    {
        log.Debug(string.Format("EXIT {0} returns {1}", GetMethodnameWithParameters(operationName, outputs), returnValue));
    }

    public object BeforeCall(string operationName, object[] inputs)
    {
        log.Debug(string.Format("ENTER {0}", GetMethodnameWithParameters(operationName, inputs)));
        return null;
    }

    private string GetMethodnameWithParameters(string operationName, object[] outputs)
    {
        var sBuilder = new StringBuilder();
        sBuilder.Append(operationName).Append("(");
        if (outputs != null)
        {
            sBuilder.Append(string.Join(",", outputs));
        }
        sBuilder.Append(")");

        return sBuilder.ToString();
    }
}

Aby udekorować nasz serwis takim interceptorem wystarczy obok atrybutu OperationContract lub ServiceContract, dodać nasz własny WCFInterceptor

[ServiceContract]
[WCFInterceptor(typeof(LogInterceptorAction))]
public interface IService1
{
    //...
}

lub jeśli chcemy zastosować to tylko do jednej operacji

[OperationContract]
[WCFInterceptor(typeof(LogInterceptorAction))]
string GetData(int value);

Zaimplementowane podejście trochę różni się od tego co jest w javie, przede wszystkim nie mamy tutaj podejścia jak z metody szablonowej, gdzie możemy wykonać operacje WCF-a w dowolnym momencie. Możliwe, że rozwiązanie mojego problemu już istnieje i niepotrzebnie wymyślałem koło od nowa, tak więc jeśli znacie jakąś klasę/bibliotekę, która mogłaby się przydać w przyszłości, dajcie znać w komentarzach. 🙂