Karsten Kempe: Screencast: Mit fünf Schritten effizienter Software entwickeln

Auf der Developer Week 2016 in Nürnberg habe ich einen Vortrag mit dem Thema „Mit fünf Schritten effizienter Software entwickeln“ gehalten. Die Aufzeichnung wurde inzwischen von der Neuen Mediengesellschaft Ulm (Danke Florian Bender!) auf YouTube veröffentlicht. Schaut Euch den Vortrag mal an und lasst gerne ein paar Kommentare hier. Eure Meinung interessiert mich sehr.

Jürgen Gutsch: Creating a container component in Angular2

In one of the last projects, I needed a shared reusable component, which needs to be extended with additional contents or functionality by the view who uses this component. In our case, it was a kind of a menu bar used by multiple views. (View in this case means routing targets.)

Creating such components was easier than expected. I anyway spent almost a whole day to find that solution, I played around with view and template providers, tried to access the template and to manipulate the template. I also tried to create an own structural directive.

But you just need to use the directive in the container component.

<nav>
  <div class="navigation pull-left">
    <ul>
      <!-- the menu items --->
    </ul>
  </div>
  <div class="pull-right">
    <ng-content></ng-content>
  </div>
</nav

That's all. You don't need to write any TypeScript code to get this working. Using this component is now pretty intuitive:

<div class="nav-bar">
  <app-navigation>
    <button (click)="printDraft($event)">print draft</button>
    <button (click)="openPreview($event)">Show preview</button>
  </app-navigation>
</div>

The contents of the - the buttons - will now be placed to the place holder.

After spending almost a whole day to get this working my first question was: Is it really that easy? Yes it is. That's all.

Maybe you knew about it. But I wasn't able to find any hint in the docs, on StackOverflow or in any Blog about it. Maybe this requirement isn't used needed often. At least I stumbled upon a documentation where ng-content as used and I decided to write about it. Hope it will help someone else. :)

Marco Scheel: Links for 2016-09-27 [del.icio.us]

Norbert Eder: Mit Scrum ins Burnout?

Auf entwickler.de wird Scrum mit Burnout in Verbindung gebracht. Im ersten Moment würde ich meinen, dass das nicht sein kann. So hat auch Patrick Koglin gleich den entsprechenden Konter parat. Patrick scheint wohl durch meinen Tweet auf dieses Thema aufmerksam geworden zu sein:

Damit wollte ich nicht meine Meinung kundtun, sondern vielmehr eine weitere Meinung in das Thema der agilen Methoden mit einbeziehen. Patrick hat hierbei versucht, die Irrtümer aus dem ursprünglichen Artikel zu korrigieren. Aber der Reihe nach.

Ein Blick auf die genannten Irrtümer

Irrtum #1: Agile führt nicht zu schnellerem Ergebnis, sondern es liefert frühere Teilinkremente die potentiell ausgeliefert und vom Kunden beurteilt werden können.

Damit hat Patrick recht, es entsprich der Grundidee. Durch kurze Iterationen frühzeitig die Möglichkeit zu erhalten, das Ergebnis zu begutachten und eventuell die Richtung ändern zu können. Das klingt in der Theorie super gut. In der Praxis KANN es passieren, dass gerade das Management dies zum Anlass nimmt, eben das schnelle Ergebnis in den Vordergrund zu stellen. Das entspricht dann zwar nicht mehr der Grundidee von Scrum, aber das macht an dieser Stelle ja nichts – kann man also schon mal machen (bitte mit entsprechendem Sarkasmus lesen).

Irrtum #2: „Ein Spagat aus Qualität, pünktliche Auslieferung und Kundenzufriedenheit geht nicht.“ Doch geht, nur im Scrum-Ansatz wird weder an Qualität, zeitlichen Terminen und Kundenzufriedenheit gespart, sondern am Inhalt.

Ja, auch hier hat Patrick recht. In der Realität wird an der Qualität gespart. Nicht weil es der Entwickler möchte, sondern weil der Druck entsprechend hoch ist, dass der Entwickler (wenn er die notwendige Unterstützung durch die Entwicklungsleitung nicht hat), da gar nicht auskommt. Auf der Strecke bleibt also Qualität, Testing und Dokumentation. Das kennen wir doch alle. Niemand findet es gut, aber es ist so.

Irrtum #3: Die Velocity und das Burndown-Chart sind Indikatoren dafür ob das was sich das Team am Beginn des Sprintes vorgenommen hat bis zum Ende des Sprintes wirklich fertig wird. Es geht um eine Selbstüberprüfung der Realisierbarkeit und ob „Störungen“ vorliegen die daran hindern.

Ja. Nochmals ja und wieder ja. Wer will, kann das Commitment des Teams auch als Versprechen nehmen und dann zusätzlich (nennen wir das die Emergency Lane) zusätzliche Features “einkippen”. Das geht sich doch locker neben dem eigentlichen Versprechen aus. Das Team weiß natürlich, dass das nicht korrekt ist, kommt aber eventuell nicht aus, da “der Befehl” von hoch oben kommt und somit dringlicher ist, als den Prozess einzuhalten.

Irrtum #4: There is no Micromanager, because agile.

Theoretischer kann diese Aussage nicht sein. Natürlich gibt es in der Scrum-Welt keine Rolle ‘Micromanager’. Leider gibt es sie in der Realität und leider hängt man sich gerne das Schild “Wir leben Scrum” um, man will ja cool sein und dazu gehören. Gelebt wird es aber oft anders. Manchmal will man es auch gar nicht verstehen, schließlich wird man in seinem Micromanagement durch Scrum doch deutlich beschnitten. Ergo, eine wahre Aussage und es sollte auch so sein, solange sich aber nicht alle an den Scrum-Prozess halten, wird das auch nichts.

Irrtum #6: Dem Gefühl, anstehende Aufgaben nicht bewältigen zu können wird doch mit konkreten User Stories die schriftlich formuliert werden, einem festen Ansprechpartner (dem Product Owner), Akzeptanzkriterien, gemeinsamen Schätzung im Team und einer Zusammenarbeit als Team sehr stark entgegengewirkt.

Wenn jemand an seinem Job hängt, dann wird er lange Todo-Listen, die vom Chief Executive Officer kommen, abarbeiten. Egal, ob das nun Teil der Iteration ist, perfekt ausgearbeitet ist, oder nicht. Natürlich kann man sich als Team stark machen, im Einzelgespräch ändert sich das Verhalten aber sehr schnell.

Irrtum #7: In Scrum werden keine Aufgaben zugewiesen (Push), sondern jeder nimmt sich per Pull-System ein der nächsten Aufgaben mit hoher Priorität.

Natürlich. In der Praxis kennen sich bestimmte Entwickler in einem bestimmten Bereich aus und andere nicht. Wer macht es? Natürlich der, der sich auskennt. Warum nicht ein anderer, um Wissen zu verteilen? Ganz klar: die Emergency Lane führt schon wieder einige Sonderpunkte und das Committment gerät ins Wanken.

Irrtum #11: Die Vermischung zwischen persönlicher und organisatorischer Ebene die sich vollständig durch diesen Artikel zieht.

Natürlich kann man diese Grenzen ziehen. Ich bin natürlich für meine Gefühle verantwortlich. Kein anderer. Aber seien wir einmal ehrlich. Wenn man tagein-tagaus immer dasselbe predigt, und dennoch wird es ignoriert, irgendwann kann man bei diesem Thema nicht mehr “cool” bleiben. Das Ergebnis ist, dass wir emotional in eine Diskussion gehen. Zieht sich das über Monate oder gar Jahre, dann kann man sich dieser Sache emotional einfach nicht mehr entziehen. Das Ergebnis kann sein, dass sich der Puls massiv erhöht, wenn eine bestimmte Person den Raum betritt (da muss noch nicht mal etwas passiert sein). Sowas fließt in die Beurteilung mit ein und das kann auch niemand unterbinden. Wird nun auf Management-Ebene der Prozess Scrum torpediert und kämpfen Entwickler jedoch für diesen Prozess, dann haben wir ein organisatorisches Problem, das sich über kurz oder lang zu einem persönlichen Problem der Entwickler entwickeln wird. Eine Trennung der Ebenen erscheint mir daher nicht als möglich.

Scrum als Druckmittel

In keinster Weise möchte ich hier Patrick Koglin zu nahe treten, ich denke er weiß schon was er sagt und tut. Ebensowenig möchte ich den ursprünglichen Artikel in Abrede stellen. Der Punkt ist, dass es sehr viele Manager gibt, die Scrum einsetzen möchte, weil man damit Entwickler anlocken kann, es aber nicht leben möchte. Deshalb nicht, weil sie dadurch “Macht” abgeben müssen. Entscheidungen werden anders getroffen, liegen nicht mehr bei einer einzelnen Person. Vielmehr ist es durch die neue Transparenz ein Leichtes, das Entwicklungsteam zu überwachen. Mit Hilfe der erhaltenen Kennzahlen kann man das Team super steuern. Und genau dieses fehlende Verständnis dafür, wie Scrum funktioniert, kann zu erheblichen Druck und in weiterer Folge zu Burnout führen.

Der Kampf gegen Windmühlen

Das Problem ist also nicht Scrum, sondern das Management, das sich etwas auf die Fahnen heftet, was es nicht bereit ist zu leben, Scrum macht es dem Management aber einfach, Druck auszuüben. Nehmen wir als Beispiel die Velocity:

Ein Scrum-Team hat sich gegen das Management durchgesetzt und schätzt die Aufgaben nun in Story Points (Schätzung nach Komplexität) und nicht mehr nach zeitlichem Aufwand. Die Velocity beschreibt nun, wie viele Story Points ein Team im Schnitt pro Iteration erledigen kann. Diese Kennzahl ist transparent und jedem zugänglich. Der Micromanager hat nun einige Möglichkeiten, den Hebel anzusetzen:

  • Er involviert sich in die Schätzung und will für höherere Komplexität geringere Story Points
  • Es muss “einfach” die Velocity erhöht werden

Natürlich, das DARF ER NICHT. Schon klar. Manche machen es trotzdem. Wir wissen, dass man dazu Scrum sagen kann (man kann auch zu einer Birne Apfel sagen), es aber kein Scrum ist. Und genau darin liegt die Gefahr, dass Scrum in ein Burnout ausartet. Warum? Weil sich durch diesen falschen Einsatz eine ständige Reibung zwischen Management und Scrum-Team gibt. Das Scrum-Team muss ständig dagegen halten und auf einer Front “arbeiten”, die nicht notwendig wäre. Don Quijote kommt mir spontan hier in den Sinn. Das zermürbt, da eine Änderung nicht möglich ist (außer den Job zu quittieren).

Fazit

Wird Scrum von allen getragen und richtig eingesetzt, halte ich eine Förderung von Burnouts für unwahrscheinlich (ohne mir Statistiken dazu angesehen zu habe – sofern verfügbar). Bei falschem Einsatz bin ich mir sicher, dass entweder die Burnout-Rate nach oben geht, oder die Fluktuation im Scrum-Team steigt.

The post Mit Scrum ins Burnout? appeared first on Norbert Eder.

Stefan Henneken: IEC 61131-3: Arrays with variable length

While declaring arrays, one had always to define a constant value up to now. Since the 3rd edition of the IEC 61131-3, arrays can be declared with a variable length. Thus, you can create functions much more generically than previously.

Although, variables can be used for array bounds, they have to be declared as constants. An adaption of the array bounds is thus not possible at runtime.

PROGRAM MAIN
VAR
  arrData             : ARRAY[1..ARRAY_UPPER_BOUND] OF INT;
END_VAR
VAR CONSTANT
  ARRAY_UPPER_BOUND   : INT := 10;  
END_VAR

Fixed array bounds represent an inconvenient limitation especially when arrays are passed to functions or function blocks as parameters. If this limitation is inacceptable, one had to switch to pointer arithmetic with all usual disadvantages. Below is a simple example, which calculates the sum of a one-dimensional array of LREAL variables.

FUNCTION F_CalcSum1DimArrayOldSchool : LREAL
VAR_INPUT
  pData           : POINTER TO LREAL;
  nSize           : UDINT;
END_VAR
VAR
  pDataIndex      : POINTER TO LREAL;
  nUpperIndex     : UDINT;
  nIndex          : UDINT;
END_VAR
 
F_CalcSum1DimArrayOldSchool := 0;
nUpperIndex := nSize / SIZEOF(pDataIndex^);
IF (nUpperIndex > 0) THEN
  FOR nIndex := 0 TO (nUpperIndex - 1) DO
    pDataIndex := pData + (nIndex * SIZEOF(pDataIndex^));
    F_CalcSum1DimArrayOldSchool := F_CalcSum1DimArrayOldSchool + pDataIndex^;   
  END_FOR
END_IF

The function can be used for the addition of arbitrary LREAL arrays. It is independent of the number of elements and of the upper and lower array bounds.

PROGRAM MAIN
VAR
  array01    : ARRAY[2..8] OF LREAL := [16.1, 34.1, 4.1, 43.1, 35.1, 2.1, 65.1];
  lrSum01    : LREAL;
     
  array02    : ARRAY[-1..2] OF LREAL := [16.1, 34.1, 9.1, 13.1];
  lrSum02    : LREAL;
     
  array03    : ARRAY[-3..-1] OF LREAL := [16.1, 34.1, 8.1];
  lrSum03    : LREAL;
END_VAR
lrSum01 := F_CalcSum1DimArrayOldSchool(ADR(array01), SIZEOF(array01));
lrSum02 := F_CalcSum1DimArrayOldSchool(ADR(array02), SIZEOF(array02));
lrSum03 := F_CalcSum1DimArrayOldSchool(ADR(array03), SIZEOF(array03));

Sample 1 (TwinCAT 3.1.4020)

However, this solution has several drawbacks. First of all, simply the fact that the pointer arithmetic has to be used. The source code of the function gets rather complex even with simple tasks. Secondly, size or length value has also to be passed to the function. When calling a function, it must be guaranteed that the array pointer and the length reference match.

Since the 3rd Edition of IEC 61131-3, array can be defined with a variable array bound. Instead of the array bound, a “*” is declared:

arrData   : ARRAY[*] OF LREAL;

If the function is called, the passed array should have constant array bounds. By means of the functions LOWER_BOUND and UPPER_BOUND, the corresponding upper and lower array bounds can be queried in the function.

Currently, arrays with a variable length can be passed only to VAR_IN_OUT variables of functions, function blocks and methods. (One would hope that VAR_INPUT and VAR_OUTPUT variables will be supported in the future.)

Here is an adjusted example:

FUNCTION F_CalcSum1DimArray : LREAL
VAR_IN_OUT
  arrData    : ARRAY[*] OF LREAL;
END_VAR
VAR
  nIndex     : DINT;
END_VAR
F_CalcSum1DimArray := 0;
FOR nIndex := LOWER_BOUND(arrData, 1) TO UPPER_BOUND(arrData, 1) DO
  F_CalcSum1DimArray := F_CalcSum1DimArray + arrData[nIndex];
END_FOR

The function expects only one array of LREAL values as an input parameter. The number of array elements is variable. An iteration over the whole array can be performed with LOWER_BOUND and UPPER_BOUND. The source code is much more readable than in the first example.

PROGRAM MAIN
VAR
  array01    : ARRAY[2..8] OF LREAL := [16.1, 34.1, 4.1, 43.1, 35.1, 2.1, 65.1];
  lrSum01    : LREAL;
     
  array02    : ARRAY[-1..2] OF LREAL := [16.1, 34.1, 9.1, 13.1];
  lrSum02    : LREAL;
     
  array03    : ARRAY[-3..-1] OF LREAL := [16.1, 34.1, 8.1];
  lrSum03    : LREAL;
END_VAR
lrSum01 := F_CalcSum1DimArray(array01);
lrSum02 := F_CalcSum1DimArray(array02);
lrSum03 := F_CalcSum1DimArray(array03);

Sample 2 (TwinCAT 3.1.4020)

Multidimensional arrays are also supported. All dimensions have to be declared as variable:

arrData    : ARRAY[*, *, *] OF LREAL;

The second parameter of UPPER_BOUND and LOWER_BOUND specifies the dimension whose respective array bounds have to be identified.

FUNCTION F_CalcSum3DimArray : LREAL
VAR_IN_OUT
   arrData      : ARRAY[*, *, *] OF LREAL;
END_VAR
VAR
   nIndex1, nIndex2, nIndex3  : DINT;
END_VAR
F_CalcSum3DimArray := 0;
FOR nIndex1 := LOWER_BOUND(arrData, 1) TO UPPER_BOUND(arrData, 1) DO
  FOR nIndex2 := LOWER_BOUND(arrData, 2) TO UPPER_BOUND(arrData, 2) DO
    FOR nIndex3 := LOWER_BOUND(arrData, 3) TO UPPER_BOUND(arrData, 3) DO
      F_CalcSum3DimArray := F_CalcSum3DimArray + arrData[nIndex1, nIndex2, nIndex3];
    END_FOR
  END_FOR
END_FOR

With the call, any three-dimensional array of LREAL values can be passed to a function.

ROGRAM MAIN
VAR
  array01    : ARRAY[1..2, 3..4, 5..6] OF LREAL := [16.1, 34.1, 4.1, 43.1, 35.1, 2.1, 65.1, 16.1];
  lrSum01    : LREAL;
END_VAR
lrSum01 := F_CalcSum3DimArray(array01);

Sample 3 (TwinCAT 3.1.4020)

Thus, more complex tasks can be implemented flexibly without making use of pointer arithmetic.

Finally, it should be demonstrated with a function block which multiplies two matrices. The sizes of the matrices are variable:

METHOD PUBLIC Multiplication : BOOL
VAR_IN_OUT
  arrayA     : ARRAY[*, *] OF DINT;
  arrayB     : ARRAY[*, *] OF DINT;
  arrayX     : ARRAY[*, *] OF DINT; 
END_VAR
VAR
  nIndex1, nIndex2, nIndex3, nIndex4   : DINT;
END_VAR;
FOR nIndex1 := LOWER_BOUND(arrayA, 1) TO UPPER_BOUND(arrayA, 1) DO
  FOR nIndex2 := LOWER_BOUND(arrayB, 2) TO UPPER_BOUND(arrayB, 2) DO
    nIndex4 := 0;
    FOR nIndex3 := LOWER_BOUND(arrayA, 2) TO UPPER_BOUND(arrayA, 2) DO
      nIndex4 := nIndex4 + arrayA[nIndex1, nIndex3] * arrayB[nIndex3, nIndex2];
    END_FOR;
    arrayX[nIndex1, nIndex2] := nIndex4;
  END_FOR;
END_FOR;

The method can be called by different-sized arrays.

PROGRAM MAIN
VAR
  fbMatrix     : FB_Matrix;
  arrayA1      : ARRAY[1..2, 1..2] OF DINT := [1, 2, 3, 4];
  arrayB1      : ARRAY[1..2, 1..2] OF DINT := [5, 6, 7, 8];
  arrayX1      : ARRAY[1..2, 1..2] OF DINT;
     
  arrayA2      : ARRAY[1..3, 1..3] OF DINT := [1, 2, 3, 4, 5, 6, 7, 8, 9];
  arrayB2      : ARRAY[1..3, 1..3] OF DINT := [5, 6, 7, 8, 10, 11, 12, 13, 14];
  arrayX2      : ARRAY[1..3, 1..3] OF DINT;             
END_VAR
fbMatrix.Multiplication(arrayA1, arrayB1, arrayX1);
fbMatrix.Multiplication(arrayA2, arrayB2, arrayX2);

Sample 4 (TwinCAT 3.1.4020)


Marco Scheel: Links for 2016-09-26 [del.icio.us]

Marco Scheel: Links for 2016-09-25 [del.icio.us]

Johannes Renatus: Angular 2.0 Released

Nach einer gefühlten Ewigkeit der Entwicklung, wurde nun endlich die erste Version von Angular 2.0 veröffentlicht. Auf der einen Seite freue ich mich über die neuen Möglichkeiten die einem geboten werden. Auf der anderen Seite stehen aber auch eine menge breaking changes die mit Angular 2.0 daher kommen, vor allem wenn man bisher alles mit […]

Johannes Renatus: Package Installer Extension für Visual Studio

Inzwischen sollte man auch als .NET Webentwickler mitbekommen haben, das es neben NuGet auch noch npm und weitere Paketmanager gibt. Hier gibt es wie auch bei NuGet eine Konsole, aber meist keine direkte Visual Studio Integration. In den meisten Fällen möchte man aber nur schnell ein Paket installieren und nicht erst in die Doku schauen […]

Marco Scheel: Links for 2016-09-24 [del.icio.us]

Marco Scheel: Links for 2016-09-23 [del.icio.us]

  • Troy Hunt: Azure Functions in practice
    I wrote recently about how Have I been pwned (HIBP) had an API rate limit introduced and then brought forward which was in part a response to large volumes of requests against the API. via Pocket

Sven Hubert: Code-Analyse und OmniSharp – dotnetpro Artikel zum Download

In der aktuellen Ausgabe der dotnetpro (10/2016) sind zwei Artikel von der AIT enthalten. Im Artikel „Code-Analyse für jedermann – Auch kostenlose Tools lassen sich in den Build-Prozess einbinden“ finden Sie Informationen zur Erweiterung der Code-Auswertung jenseits von ReSharper. Bei „OmniSharp – Wenn die Programmiersprache die Wahl des Editors nicht mehr beeinflusst“ erhalten Sie einen Einblick, wie Sie Sublime und weitere Editoren um C# Unterstützung erweitern.

Beide Artikel stehen für Sie in unserem Downloadbereich zum Herunterladen bereit.

dotnetpro Ausgabe 10/2016

dotnetpro Ausgabe 10/2016

Marco Scheel: Links for 2016-09-22 [del.icio.us]

codefest.at [MS]: Wir ziehen um!!

Die Codefest Seite hat uns über viele Jahre begleitet und ist uns richtig ans Herz gewachsen. Irgendwann wird es dann aber doch Zeit für einen Tapetenwechsel. Daher:

  • Adieu Blogengine .NET!
  • Hello Wordpess!!

Unsere neue Adresse wird die alte sein. Derzeit haben wir allerdings noch einen Parallelbetrieb.

Bis daher der redirect eingerichtet ist und die automatische Weiterleitung funktioniert besucht uns doch schon jetzt auf: https://blogs.msdn.microsoft.com/codefest/

https://blogs.msdn.microsoft.com/codefest/

Jürgen Gutsch: Authentication in ASP.​NET Core for your Web API and Angular2

Authentication in a single page application is a bit more special, if you just know the traditional ASP.NET way. To imagine that the app is a completely independent app like a mobile app helps. Token based authentication is the best solution for this kind of apps. In this post I'm going to try to describe a high level overview and to show a simple solution.

Intro

As written in my last posts about Angular2 and ASP.NET Core, I reduced ASP.NET Core to just a HTTP Service, to provide JSON based data to an Angular2 client. Some of my readers, asked me about how the Authentication is done in that case. I don't use any server generated log-in page, registration page or something like this. So the ASP.NET Core part only provides the web API and the static files for the client application.

There are many ways to protect your application out there. The simplest one is using an Azure Active Directory. You could also setup a separate authentication server, using IdentityServer4, to manage the users, roles and to provide a token based authentication.

And that's the key word: A Token Based Authentication is the solution for that case.

With the token bases authentication, the client (the web client, the mobile app, and so on) gets a string based encrypted token after a successful log-in. The token also contains some user info and an info about how long the token will be valid. This token needs to be stored on the client side and needs to be submitted to the server every time you request a resource. Usually you use a HTTP header to submit that token. If the token is not longer valid you need to perform a new log-in.

In one of our smaller projects, didn't set-up a different authentication server and we didn't use Azure AD, because we needed a fast and cheap solution. Cheap from the customers perspective.

The Angular2 part

On the client side we used angular2-jwt, which is a Angular2 module that handles authentication tokens. It checks the validity, reads meta information out of it and so on. It also provides a wrapper around the Angular2 HTTP service. With this wrapper you are able to automatically pass that token via a HTTP header back to the server on every single request.

The work flow is like this.

  1. If the token is not valid or doesn't exist on the client, the user gets redirected to the log-in route
  2. The user enters his credentials and presses the log-in button
  3. The date gets posted to the server where a special middle-ware handles that request
    1. The user gets authenticated on the server side
    2. The token, including a validation date and some meta date, gets created
    3. The token gets returned back to the client
  4. the client stores the token in the local storage, cookie or whatever, to use it on every new request.

The angular2-jwt does the most magic on the client for us. We just need to use it, to check the availability and the validity, every time we want to do a request to the server or every time we change the view.

This is a small example (copied from the Github readme) about how the HTTP wrapper is used in Angular2:

import { AuthHttp, AuthConfig, AUTH_PROVIDERS } from 'angular2-jwt';

...

class App {

  thing: string;

  constructor(public authHttp: AuthHttp) {}

  getThing() {
    // this uses authHttp, instead of http
    this.authHttp.get('http://example.com/api/thing')
      .subscribe(
        data => this.thing = data,
        err => console.log(err),
        () => console.log('Request Complete')
      );
  }
}

More samples and details can be found directly on github https://github.com/auth0/angular2-jwt/ and there is also a detailed blog post about using angular2-jwt: https://auth0.com/blog/introducing-angular2-jwt-a-library-for-angular2-authentication/

The ASP.NET part

On the server side we also use a, separate open source project, called SimpleTokenProvider. This is really a pretty simple solution to authenticate the users, using his credentials and to create and provide the token. I would not recommend to use this way in a huge and critical solution, in that case you should choose the IdentiyServer or any other authentication like Azure AD to be more secure. The sources of that project need to be copied into your project and you possibly need to change some lines e. g. to authenticate the users against your database, or whatever you use to store the user data. This project provides a middle-ware, which is listening on a defined path, like /api/tokenauth/. This URL is called with a POST request by the log-in view of the client application.

The authentication for the web API, is just using the token, sent with the current request. This is simply done with the built-in IdentiyMiddleware. That means, if ASP.NET MVC gets a request to a Controller or an Action with an AuthorizeAttribute, it checks the request for incoming Tokens. If the Token is valid, the user is authenticated. If the user is also in the right role, he gets authorized.

We put the users role information as additional claims into the Token, so this information can be extracted out of that token and can be used in the application.

To find the users and to identify the user, we use the given UserManager and SignInManager. These managers are bound to the IdentityDataContext. This classes are already available, when you create a new project with Identiy in Visual Studio.

This way we can authenticate a user on the server side:

public async Task<ClaimsIdentity> GetIdentity(string email, string password)
{
    var result = await _signInManager.PasswordSignInAsync(email, password, false, lockoutOnFailure: false);
    if (result.Succeeded)
    {
        var user = await _userManager.FindByEmailAsync(email);
        var claims = await _userManager.GetClaimsAsync(user);

        return new ClaimsIdentity(new GenericIdentity(email, "Token"), claims);
    }

    // Credentials are invalid, or account doesn't exist
    return null;
}

And this claims will be used to create the Jwt-Token in the TokenAuthentication middle-ware:

var username = context.Request.Form["username"];
var password = context.Request.Form["password"];

var identity = await identityResolver.GetIdentity(username, password);
if (identity == null)
{
    context.Response.StatusCode = 400;
    await context.Response.WriteAsync("Unknown username or password.");
    return;
}

var now = DateTime.UtcNow;

// Specifically add the jti (nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new[]
{
    new Claim(JwtRegisteredClaimNames.Sub, username),
    new Claim(JwtRegisteredClaimNames.Jti, await _options.NonceGenerator()),
    new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(now).ToString(), ClaimValueTypes.Integer64)
};

// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
    issuer: _options.Issuer,
    audience: _options.Audience,
    claims: claims,
    notBefore: now,
    expires: now.Add(_options.Expiration),
    signingCredentials: _options.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

var response = new
{
    access_token = encodedJwt,
    expires_in = (int)_options.Expiration.TotalSeconds,
    admin = identity.IsAdministrator(),
    fullname = identity.FullName(),
    username = identity.Name
};

// Serialize and return the response
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings));

This code will not work, if you copy and past it in your application, but shows you what needs to be done to create a token and how the token is created and sent to the client. Nate Barbattini wrote a detailed article about how this SimpleTokenProvider is working and how it needs to bes used in his Blog: https://stormpath.com/blog/token-authentication-asp-net-core

Conclusion

This is jsut a small overview. If you want to learn more and detailed information about how ASP.NET Identity works, you should definetly subscribe to the blogs of Dominick Baier and Brock Allen. Even the ASP.NET Docs are good resources to learn more about the ASP.NET Security.

Update: Just a few hours ago Scott Brady wrote an blog post about getting Started with IdentityServer 4

Marco Scheel: Links for 2016-09-21 [del.icio.us]

Holger Schwichtenberg: Neues E-Book zu Entity Framework Core

Das Buch, das die fertige Version 1.0 von Microsoft neuem OR-Mapper beschreibt, ist in einer ersten Grundversion mit 100 Seiten erschienen.

codefest.at [MS]: API Summit am 22.11. & 23.11. 2016 in Berlin

image

Vom 22. bis 23. November 2016 präsentiert die Entwickler Akademie in Kooperation mit dem Entwickler Magazin zum ersten Mal den API Summit in Berlin. Die neue Konferenz für Web APIs mit Java, .NET und Node.js bietet mit mehr als 26 Sessions und insgesamt 18 Speakern umfassendes Know-how zu RESTful Web APIs.

Plattformunabhängige Best Practices und Standards am ersten Tag werden mit Sessions über die Implementierung und den Betrieb von Web APIs am zweiten Tag kombiniert.

See you there…

Karsten Kempe: ALMDays goes Technical Summit

techsummitEs gibt eine Änderung für Euren Konferenz-Kalender zu beachten. Die ALMDays, so wie Ihr sie kanntet, wird es in Zukunft nicht mehr geben. Aber das ist kein Grund, traurig zu sein, denn es gibt etwas noch Besseres für Euch. ALMDays goes Technical Summit!

Wer geplant hatte die ALMDays (wieder) zu besuchen, um Informationen über die Visual Studio Familie aus erster Hand zu bekommen, für den sind folgende News besonders wertvoll. Um die Veranstaltung auf das nächste Level zu heben und sowohl Themen- als auch Teilnehmerspektrum zu erweitern, hat sich Microsoft entschieden, die kommenden ALM Days gemeinsam mit dem Technical Summit 2016 von 5.-8. Dezember in Darmstadt durchzuführen.

Der Technical Summit hat sich in den letzten Jahren zu einer der wichtigsten Konferenzen für Entwicklungs- und Infrastrukturthemen in Deutschland entwickelt. Unter dem Motto „connecting.technologies“ werden in diesem Jahr nun aktuelle Trends und Technologien rund um Enterprise Software Development, Web Development, Cloud Computing, Data Science, Game- und Mobile -Development sowie zu Entwicklungsmethodik und Agilität vorgestellt. Themen rund um ALM und DevOps stehen dabei mit im Vordergrund.

Durch diese Fusion bekommt die Konferenz, meiner Meinung nach, noch mehr Qualität und Ihr könnt mit noch mehr Experten über die Neuigkeiten in der Software-Entwicklung sprechen.

Der absolute Hammer sind die Keynote-Sprecher: Scott Guthrie (Executive Vice President Cloud), Erich Gamma (Gang of four), Ann Johnson (Vice President Cybersecurity) und Donovan Brown (DevOps Program Manager). Entwicklerherz, was willst Du mehr?!

Ich bin natürlich auch dabei und werde zusammen mit Neno Loje einen Workshop mit dem Fokus „DevOps und Continuous Delivery“ durchführen!

Wir sehen uns am Summit!

Jürgen Gutsch: ASP.​NET Core and Angular2 using dotnet CLI and Visual Studio Code

This is another post about ASP.NET Core and Angular2. This time I use a cleaner and more light weight way to host a Angular2 App inside an ASP.NET Core Web. I'm going to use dotnet CLI and Visual Studio Code.

A few days ago there was an update for ASP.NET Core announced. This is not a big one, but a important run-time update. You should install it, if you already use ASP.NET Core 1.0. If you install it the first time (loaded from http://get.asp.net/), the update is already included. Also since a few days, the final version of Angular2 was announced. So, we will use Angular 2.0.0 and ASP.NET Core 1.0.1.

This post is structured into nine steps:

#1 Create the ASP.NET Core web

The first step is to create the ASP.NET Core web application this is the easiest thing using the dotnet CLI. After downloading it from http://get.asp.net and installing it, you are directly able to use it. Choose any console you like and g to your working folder.

Type the following line to create a new web application inside that working folder:

> dotnet new -t web

If you used the dotnet CLI for the first time it will take a few seconds. After the first time it is pretty fast.

Now you have a complete ASP.NET Core quick-start application. Almost the same thing you get, if you create a new application in Visual Studio 2015.

Now we need to restore the NuGet packages, which contains all the .NET Core and ASP.NET dependencies

> dotnet restore

This takes a few seconds, depending in the amount of packages or on the internet connection.

If this is done, type dotnet run to start the app. You will see an URL in the console. Copy this URL and paste it into the browsers address bar. As you can see, you just need three console commands to create a working ASP.NET application.

#2 Setup the ASP.NET Core web

To support a Angular2 single page application we need to prepare the Startup.cs a little bit. Because we don't want to use MVC, but just the web API, we need to remove the configured default route.

To support Angular routing, we need to handle 404 errors: In case a requested resource was not found on the server, it could be a Angular route. This means we should redirect request, which results in a error 404, to the index.html. We need to create this file in the wwwroot folder later on.

The Configure method in the Startup.cs now looks like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.Use(async (context, next) =>
    {
        await next();

        if (context.Response.StatusCode == 404
            && !Path.HasExtension(context.Request.Path.Value))
        {
            context.Request.Path = "/index.html";
            await next();
        }
    });

    app.UseStaticFiles();

    app.UseIdentity();

    app.UseMvc();
}

#3 The front-end dependencies

To develop the front-end with Angular 2, we need some tools, like TypeScript, Webpack and NPM. We use TypeScript to write the client code, which will be transpiled to JavaScript using Webpack. We use Webpack with a simple Webpack configuration to transpile the TypeScript code to JavaScript and to copy the dependencies to the wwwroot folder.

NPM is used to get all that stuff, including Angular itself, to the development machine. We need to configure the package.json a little bit. The easiest way is to use the same configuration as in the ANgular2 quick-start tutorial on angular.io

You need to have Node.JS installed on your machine, To get all the tools working.

{
  "name": "webapplication",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "lite": "lite-server",
    "postinstall": "typings install",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "typings": "typings"
  },
  "dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/router": "3.0.0",
    "@angular/upgrade": "2.0.0",

    "core-js": "2.4.1",
    "reflect-metadata": "0.1.3",
    "rxjs": "5.0.0-beta.12",
    "systemjs": "0.19.27",
    "zone.js": "0.6.21",
    
    "bootstrap": "3.3.6"
  },
  "devDependencies": {
    "ts-loader": "0.8.2",
    "ts-node": "0.5.5",
    "typescript": "1.8.10",
    "typings": "1.3.2",
    "webpack": "1.13.2"
  }
}

You should also install Webpack, Typings and TypeScript globaly on your machine:

> npm install -g typescript
> npm install -g typings
> npm install -g webpack

The TypeScript build needs a configuration, to know how to build that code. This is why we need a tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  }
}

And TypeScript needs type defintions for all the used libraries, which are not written in TypeScript. This is where Typings is used. Typings is a kind of a package manager for TypeScript type definitions, which also needs a configuration:

{
  "globalDependencies": {
    "core-js": "registry:dt/core-js#0.0.0+20160725163759",
    "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
    "node": "registry:dt/node#6.0.0+20160909174046"
  }
}

Now we can use npm install in the console to load all that stuff. This command automatically calls typings install as a NPM post install event.

#4 Setup the single page

The Angular2 app is hosted on a single HTML page inside the wwwroot folder of the ASP.NET Core web. Add a new index.html and add it to the wwwroot folder:

<html>
    <head>
        <title>Angular 2 QuickStart</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="css/site.css">
        <!-- 1. Load libraries -->
        <script src="js/core.js"></script>
        <script src="js/zone.js"></script>
        <script src="js/reflect.js"></script>
        <script src="js/system.js"></script>
        <!-- 2. Configure SystemJS -->
        <script src="systemjs.config.js"></script>
        <script>
          System.import('app').catch(function(err){ console.error(err); });
        </script>
    </head>
    <!-- 3. Display the application -->
    <body>
        <my-app>Loading...</my-app>
    </body>
</html>

Currently we don't have the JavaSript dependencies configured. This is what we will do in the next step

#5 configure webpack

Webpack has two tasks in this simple tutorial. The first thing is to copy some dependencies out of the node_modules folder into the wwwroot folder, because static files will only be provided out of this special folder. We need Core.JS, Zone.JS, Reflect-Metadata and System.JS. The second task is to build and bundle the Angular2 application (which is not yet written) and all it's dependencies.

Let's see how this simple Webpack configuration (webpack.config.js) looks like:

module.exports = [
  {
    entry: {
      core: './node_modules/core-js/client/shim.min.js',
      zone: './node_modules/zone.js/dist/zone.js',
      reflect: './node_modules/reflect-metadata/Reflect.js',
      system: './node_modules/systemjs/dist/system.src.js'
    },
    output: {
      filename: './wwwroot/js/[name].js'
    },
    target: 'web',
    node: {
      fs: "empty"
    }
  },
  {
    entry: {
      app: './wwwroot/app/main.ts'
    },
    output: {
      filename: './wwwroot/app/bundle.js'
    },
    devtool: 'source-map',
    resolve: {
      extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
    },
    module: {
      loaders: [
        { test: /\.ts$/, loader: 'ts-loader' }
      ]
    }
  }];

We have two separate configurations for the mentioned tasks. This is not the best way how to configure Webpack. E.g. the Angular2 Webpack Starter or the latest Angular CLI, do the whole stuff with a more complex Webpack configuration.

To run this configuration, just type webpack in the console. The first configuration writes out a few warnings, but will work anyway. The second config should fail, because we don't have the Angular2 app yet.

#6 Configure the App

We now need to load the Angular2 app and it's dependencies. This is done with System.JS which also needs a ocnfiguration. We need a systemjs.config.js:

/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': '../node_modules/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',
            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
            // other libraries
            'rxjs': 'npm:rxjs',
        },
        meta: {
            './app/bundle.js': {
                format: 'global'
            }
        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: './bundle.js',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        }
    });
})(this);

This file is almost equal to the file from the angular.io quick-start tutorial. We just need to change a few things:

The first thing is the path to the node_modules which is not on the same level as usual. So we need to change the path to ../node_modules/, we also need to tell System.js that the bundle is not a commonjs module. this is doen with the meta property. I also changed the app main path to ./bundle.js, instead of main.js

#7 Create the app

Inside the wwwroot folder, create a new folder called app. Inside this new folder we need to create a first TypeScript file called main.ts:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

This script calls the app.module.ts, which is the entry point to the app:

import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { PersonService } from './person.service';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule],
    declarations: [AppComponent],
    providers: [
        PersonService,
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

The module collects all the parts of our app and puts all the components and services together.

This is a small component with a small inline template:

import { Component, OnInit } from '@angular/core';
import { PersonService, Person } from './person.service';

@Component({
    selector: 'my-app',
    template: `
    <h1>My First Angular 2 App</h1>
    <ul>
    <li *ngFor="let person of persons">
    <strong></strong><br>
    from: <br>
    date of birth: 
    </li>
    </ul>
    `,
    providers: [
        PersonService
    ]
})
export class AppComponent extends OnInit {

    constructor(private _service: PersonService) {
        super();
    }

    ngOnInit() {
        this._service.loadData().then(data => {
            this.persons = data;
        })
    }

    persons: Person[] = [];
}

At least, we need to create a service which calls a ASP.NET Core web api. We need to create the API later on.

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class PersonService {
    constructor(private _http: Http) { }

    loadData(): Promise<Person[]> {
        return this._http.get('/api/persons')
            .toPromise()
            .then(response => this.extractArray(response))
            .catch(this.handleErrorPromise);
    }    

    protected extractArray(res: Response, showprogress: boolean = true) {
        let data = res.json();
        return data || [];
    }

    protected handleErrorPromise(error: any): Promise<void> {
        try {
            error = JSON.parse(error._body);
        } catch (e) {
        }

        let errMsg = error.errorMessage
            ? error.errorMessage
            : error.message
                ? error.message
                : error._body
                    ? error._body
                    : error.status
                        ? `${error.status} - ${error.statusText}`
                        : 'unknown server error';

        console.error(errMsg);
        return Promise.reject(errMsg);
    }
}
export interface Person {
    name: string;
    city: string;
    dob: Date;
}

#8 The web API

The web api is pretty simple in this demo, just to show how it works:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace demo
{
    [Route("api/persons")]
    [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true, Duration = -1)]
    public class PersonsController : Controller
    {
        [HttpGet]
        public IEnumerable<Person> GetPersons()
        {
            return new List<Person>
            {
                new Person{Name = "Max Musterman", City="Naustadt", Dob=new DateTime(1978, 07, 29)},
                new Person{Name = "Maria Musterfrau", City="London", Dob=new DateTime(1979, 08, 30)},
                new Person{Name = "John Doe", City="Los Angeles", Dob=new DateTime(1980, 09, 01)}
            };
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public string City { get; set; }
        public DateTime Dob { get; set; }
    }

}

If you start the app using dotnet run you can call the API using that URL: http://localhost:5000/api/persons/, you'll see the three persons in the browser as a JSON result.

#9 That's it. Run the app.

Type webpack and dotnet run in the console to compile and pack the client app and to start the application. After that call the URL http://localhost:5000/ in a browser:

Conclusion

As you can see, hosting an Angular2 app inside ASP.NET Core web using this way is pretty much easier and much more light weight than using Visual Studio 2015.

Aniway, this is the last post about combining this two technologies. Because this is just a good way, if you write a small application. For bigger applications you should separate the client application from the server part. The Angular2 app should be written using Anngular CLI. Working like this both parts are completely independent and it is much easier to setup and to deploy.

I pushed the demo code to GitHub. Try it out, play around with it and give mes some feedback about it :)

Manfred Steyer: Angular 2 AOT Compilation and Tree Shaking with Webpack2 and/or Rollup: Step by Step

In the last days, I've adapted my Angular 2 sample for ahead-of-time (AOT) compilation with the template-compiler. Here I'm documenting the necessary steps for such an undertaking as well as my learnings towards this. The whole sample can be found here.

I'm subdividing this writing into two parts:

  1. Part 1: Angular 2 AOT Compiler and Tree Shaking with Webpack2 and/or Rollup (this one)
  2. Part 2: Angular 2 AOT Compiler and Lazy Loading (coming up)

I want to explicitly thank Wassim Chegham and Minko Gechev for providing great information about this topic. They really helped me getting started with it. Another valuable source for this topic is the cookbook for AOT and Tree Shaking within the official documentation of Angular 2.

Refactoring

As AOT compilation does not allow for dynamic references, using require is not possible. Currently, there are also problems with using module.id to work with relative paths for templates or styles. However, thanks to the angular2-template-loader for Webpack, which lines-in the templates and styles for components, we can use relative references in JIT mode anyway. In addition to that, the AOT compiler also supports relative paths:

@Component({
    selector: 'flug-suchen',
    templateUrl:  './flug-suchen.component.html',
    styleUrls: ['./flug-suchen.component.css'],
})
export class FlugSuchenComponent {
    [...]
}

Packages

In order to work with AOT compilation as well as with Webpack2 and Rollup, one has to install several packages. The next listing contains the dev-dependencies I've used for this:

"webpack": "^2.1.0-beta.21",
"angular2-template-loader": "^0.5.0",
"awesome-typescript-loader": "^2.2.4",
"file-loader": "^0.8.5",
"html-loader": "^0.4.3",
"raw-loader": "^0.5.1",

"rollup": "^0.35.11",
"rollup-plugin-commonjs": "^4.1.0",
"rollup-plugin-node-globals": "^1.0.7",
"rollup-plugin-node-resolve": "^2.0.0",
"rollup-plugin-uglify": "^1.0.1",

We also need the compiler-cli and plattform-server as a dependency:

"@angular/compiler-cli": "^0.6.2",
"@angular/platform-server": "^2.0.0",

And then, we also need TypeScript 2 and some typings as part of our dev-dependencies:

"typescript": "^2.0.2",
"@types/es6-shim": "0.0.31",
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.38",
"@types/protractor": "^1.5.18",

tsconfig.json

The AOT compiler uses its own tsconfig.json-file. According to the docs, I've named it tsconfig.aot.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "declaration": false,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "sourceMap": true,
    "pretty": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "noImplicitAny": false,
    "noImplicitReturns": false,
    "noImplicitUseStrict": false,
    "noFallthroughCasesInSwitch": true,
    "outDir": "./dist/unbundled-aot",
    "types": [
      "es6-shim",
      "node",
      "jasmine"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "angularCompilerOptions": {
   "genDir": "aot",
   "skipMetadataEmit" : true
 }
}

Here, it's important to use the target es5 together with the module-format es2015. The latter one allows for tree shaking with Rollup. The property genDir within angularCompilerOptions defines, where the template compiler should place files it generated out of HTML files.

The tsconfig.json that is used with the traditional TypeScript Compiler looks like this:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "outDir": "dist/unbundled",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "types": [
      "es6-shim",
      "node",
      "jasmine"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

Please note, that I'm using different output directories (see property outDir) for both files. This prevents that the two compilation steps mess with each other.

Compiling the templates

The following NodeJS script, I've set up within package.json, compiles the HTML templates and creates TypeScript files for it:

"ngc": "ngc -p tsconfig.aot.json --locale=en-US",

Due to a bug in version 2.0.0 I had to directly specify a locale. Otherwise most build-in pipes, like date or decimal fail in AOT-mode.

To start this script, one has to use npm run ngc.

Bootstrapping for AOT

To bootstrap Angular 2 in AOT mode, it has to be started with platformBrowser().bootstrapModuleFactory:

// main.aot.ts

import { platformBrowser } from '@angular/platform-browser';
import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';

platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).catch(err => console.error(err));

AppModuleNgFactory is a class, the AOT compiler created for the root module AppModule.

Webpack2 Configuration for AOT

The next listing shows the (simple) WebPack configuration I'm using for the AOT sample. It is pointing to two entry points. One of them points to the necessary polyfills and the other one points to the above described main.aot.ts that is bootstrapping Angular in AOT mode.

// webpack.aot.config.js

var webpack = require('webpack');
var CompressionPlugin = require("compression-webpack-plugin");

var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;

module.exports = {

    debug: false,
    profile: true,
    devtool: false,
    entry: {
        'polyfills': './app/polyfills',
        'app': './app/main.aot'
    },
    output: {
        path: __dirname + "/dist/aot",
        filename: "[name].js",
        publicPath: "dist/"
    },
    resolve: {
        extensions: ['', '.ts', '.js', '.jpg', '.jpeg', '.gif', '.png', '.css', '.html']
    },
    module: {
        loaders: [
            { test: /\.(jpg|jpeg|gif|png)$/, loader:'file-loader?name=img/[path][name].[ext]' },
            { test: /\.(eof|woff|woff2|svg)$/, loader:'file-loader?name=img/[path][name].[ext]' },
            { test: /\.css$/, loader:'raw-loader' },
            { test: /\.html$/,  loaders: ['html-loader'] },
            { test: /\.ts$/, loaders: ['awesome-typescript-loader'], exclude: /node_modules/}
        ],
        exprContextCritical: false,
    },
    modulesDirectories: ['node_modules'],
    plugins: [
        new webpack.LoaderOptionsPlugin({
            minimize: true,
            debug: false
        }),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            },
            output: {
                comments: false
            },
            sourceMap: false
        }),
        new CompressionPlugin({
            asset: "[path].gz[query]",
            algorithm: "gzip",
            test: /\.js$|\.html$/,
            threshold: 10240,
            minRatio: 0.8
        })
    ],
    node: {
        __filename: true
    },
    devServer: {
        inline:true,
        port: 8080,
        historyApiFallback: true,
        watchOptions: {
            aggregateTimeout: 300,
            poll: 1000
        }
    }

};

The plugins LoaderOptionsPlugin and UglifyJsPlugin reduce the bundle size to a minimum by means of minification and tree shaking.

Webpack2-Configuration for JIT

I've also created a JIT-based build. In principle, the WebPack configuration for this looks like the one for AOT. But its entry point app points to the file main.jit.ts which is using Angular's JIT-compiler. Another difference is that it is using the angular2-template-loader which inlines component templates.

[...]
entry: {
    'polyfills': './app/polyfills',
    'app': './app/main.jit'
},
output: {
    path: __dirname + "/dist/jit",
    filename: "[name].js",
    publicPath: "dist/"
},
resolve: {
    extensions: ['', '.ts', '.js', '.jpg', '.jpeg', '.gif', '.png', '.css', '.html']
},
module: {
    loaders: [
        { test: /\.(jpg|jpeg|gif|png)$/, loader:'file-loader?name=img/[path][name].[ext]' },
        { test: /\.(eof|woff|woff2|svg)$/, loader:'file-loader?name=img/[path][name].[ext]' },
        { test: /\.css$/, loader:'raw-loader' },
        { test: /\.html$/,  loaders: ['html-loader'] },
        { test: /\.ts$/, loaders: ['angular2-template-loader', 'awesome-typescript-loader'], exclude: /node_modules/}
    ]
}
[...]

Rollup-Configuration for AOT

To find out, whether Rollup brings improvements over just using webpack2, I've used the rollup configuration of the cookbook for AOT and rollup from angular.io:

import rollup      from 'rollup'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs    from 'rollup-plugin-commonjs';
import uglify      from 'rollup-plugin-uglify'

export default {
    entry: 'dist/unbundled-aot/app/main.aot.js',
    dest: 'dist/build.js', // output a single application bundle
    sourceMap: false,
    format: 'iife',
    plugins: [
        nodeResolve({jsnext: true, module: true}),
        commonjs({
            include: 'node_modules/rxjs/**',
        }),
        uglify()
    ]
}

As Rollup only works with JavaScript files that make use of the EcmaScript module system, I'm using the compiled version of main.aot.ts as entry point. According to the used tsconfig.aot.json, this file is located at dist/unbundled-aot/app/main.aot.js.

Build-Scripts

To build the solution for all three scenarios, I'm using the following npm scripts:

"build-all": "npm run webpack:jit && npm run webpack:aot && npm run rollup:aot",
"webpack:aot": "ngc -p tsconfig.aot.json --locale=en-US && webpack --config webpack.aot.config.js",
"webpack:jit": "webpack --config webpack.jit.config.js",
"rollup:aot": "ngc -p tsconfig.aot.json --locale=en-US && .\\node_modules\\.bin\\rollup -c rollup.js",

The command npn run build-all is starting all three builds. During its execution, Rollup will give you the following info several times:

The `this` keyword is equivalent to `undefined` at the top level of an ES module, and has been rewritten

According to the docs at angular.io this information can be safely ignored.

Results

The bundle for the JIT mode had 819 kB:

      Asset     Size  Chunks             Chunk Names
     app.js   819 kB       0  [emitted]  app

polyfills.js 97.1 kB 1 [emitted] polyfills

When it comes to AOT mode, the bundle could be reduced by 31.4 % to 562 kB:

      Asset     Size  Chunks             Chunk Names
     app.js   562 kB       0  [emitted]  app

polyfills.js 97 kB 1 [emitted] polyfills

The AOT bundle created with rollup just had 455.861 bytes (445.2 kB):

17.09.2016  18:13           455.861 build.js

Compared to the bundle for JIT mode, this brings a reduction of 45.6 %.

Starting the samples

To start the three samples, I've created three HTML files: index.jit.html for the JIT mode, index.aot.html for the AOT mode with webpack bundles and index.rollup.aot.html for AOT mode with the rollup bundle. Those files are also included in the provided sample.

Holger Schwichtenberg: Bug beim TFS-Checkin in Visual Studio: "Value was either too large or too small for a UInt32".

Ursache für diese Fehlermeldung sind ungespeicherte Dateien in der Entwicklungsumgebung.

Golo Roden: Ich kenne was, was Du nicht kennst: Geolocation API

Für zahlreiche Anwendungen kann es interessant sein, den Standort des Anwenders zu ermitteln. Während das im Webbrowser ein Leichtes ist, sieht es auf dem Server deutlich schwieriger aus …

Fabian Deitelhoff: LEGO Mindstorms EV3: Kostenlose Lehrerschulungen

Ab dem September 2016 gibt es in ganz Deutschland zahlreiche Lehrerschulungen zum LEGO Mindstorms Education EV3. Die Fortbildungen richten sich an Lehrer und Lehrerinnen weiterführender Schulen, die MINT-Fächer unterrichten. Eine umfangreiche Liste aller Schulungsorte inklusive Zeiten gibt es auf der LEGO Education Webseite. Die Buchungen erfolgen über  XING Events.

Die Anmeldung für die Schulungen läuft bereits und ist kostenfrei. Wer also schon immer mal den LEGO Mindstorms Education EV3 kennen lernen wollte und etwas zum Einsatz im Unterricht erfahren möchte, ist herzlich eingeladen. Vorerfahrungen mit dem LEGO Mindstorms Education EV3 sind ausdrücklich nicht erforderlich. Die Schulungen richten sich an Anfänger und Anfängerinnen.

Ich bin einer der Schulungsleiter für einige Termine in Nordrhein-Westfalen und in folgenden Städten dabei:

  • Paderborn
  • Wuppertal
  • Mülheim an der Ruhr
  • Herdecke
  • Hürth
  • Osnabrück
  • Krefeld
  • Hamm
  • Köln
  • Bonn

Ich freue mich an Bord sein zu dürfen und den LEGO Mindstorms Education EV3 vorstellen zu können. Bei Fragen, vor oder nach den Schulungen, freue ich mich immer über eine Nachricht.

Holger Schwichtenberg: RESTier auf der Zielgeraden zu Version 1.0

Microsofts neue Bibliothek für die einfache Implementierung von OData-Diensten hat einen Sprung von Version 0.6 auf Version 1.0 Beta gemacht.

Uli Armbruster: Vorlage zum Antworten bei unberechtigten Mahnungen

Kürzlich erhielt meine Oma ein Mahnschreiben der Sirius Inkasso GmbH, vertreten durch die Fachanwälte Wagner, Pauls und Kalb, welche eine Forderung der Communication Services Tele2 GmbH eintreiben wollten. Die Mahnung war in ihrem Fall völlig ungerechtfertigt und höchst fragwürdig, vermutlich Teil einer größeren, breit angelegten Abmahnwelle:

 

mahnung.

 
Ich habe mir daraufhin die Mühe gemacht und entsprechende Informationen für ein Antwortschreiben zusammengetragen. Darüber hinaus habe ich meinen Lieblingspassus aus dem Datenschutzrecht (zum Blog Post) angehängt, um denen – salopp gesagt – ans Bein zu pissen.

Meiner Meinung nach sollte man immer auf solche Schreiben antworten – idealerweise per Einschreiben. Im vorliegenden Fall habe ich das jedoch per E-Mail gemacht. Gerne könnt ihr die Vorlage nutzen, aber wie immer ohne Gewähr! Die Stellen, die ihr mit eigenen Angaben ausfüllen müsst, habe ich entsprechend markiert. Streicht ggf. die Passagen, die nicht passen sollten. Und wenn keine Antwort kommt, zögert nicht das an den Datenschutzbeauftragten des Bundeslandes des Inkassobüros weiterzuleiten. So etwas kann – leider sehr langsam – am Ende sehr teuer für die werden.

 

Sehr geehrte Damen und Herren,

bezugnehmend auf Ihr Schreiben vom {{Datum}} teile ich Ihnen hiermit mit, dass ich bezüglich Ihres Mahnschreibens sowohl die Hauptforderung als auch Ihren Anspruch auf jegliche Inkassokosten vollumfänglich bestreite.

Ich weise darauf hin, dass Ihrem Schreiben eine Bevollmächtigung im Original seitens Ihres Mandanten für den Inkassoauftrag gem. § 174 BGB nicht beilag.

Zudem befindet sich in Ihrem Schreiben keine Originale Abtretungsurkunde gem. § 410 BGB bei.

Außerdem möchte ich darauf hinweisen, dass eine Angabe des Rechnungsdatums fehlt. Falls das Rechnungsdatum der Tag des Vertragsabschlusses war, ist die Forderung sowieso verjährt, da seither bereits 3 Jahre verstrichen sind.

Eine ladungsfähige Anschrift des Forderungsstellers geht aus Ihrem Schreiben nicht hervor. Solange Sie Ihre Mandantin bzw. den ursprünglichen Forderungssteller nicht gegenüber mir als rechtsfähige, bestimmbare Partei identifizieren, bestreite ich schon Ihre Aktivlegitimation zur Beitreibung der Forderung.

Wie bereits ausgeführt, wird daher die Forderung vollumfänglich bestritten. Dies haben Sie dem Forderungssteller mitzuteilen. Forderungen, die weder qualifiziert dargelegt noch in der Sache begründet sind, und die dem Forderungssteller daher nicht zustehen, werde ich nicht begleichen.

Einem gerichtlichen Mahnbescheid werde ich fristgemäß widersprechen. Von weiteren Mahnschreiben an meine Adresse ist Abstand zu nehmen. Die bei unseriösen Inkassobüros übliche Taktik, mit fortwährenden Mahnbelästigungen die Zahlung eines ungerechtfertigten Anspruchs zu erreichen, verfängt bei mir nicht.

Ich warne unter Hinweis auf § 28a BDSG eindringlich vor widerrechtlicher Weitergabe meiner Daten an die SCHUFA oder andere Datenbanken. Eine solche ungerechtfertigte Maßnahme werde ich mit einstweiliger Verfügung sowie ggf. Schadenersatzforderungen aus § 824 BGB sowie mit Beschwerde an den zuständigen Landesdatenschutzbeauftragten beantworten.

Sie haben mir den Zugang des Schreibens sowie die Unterlassung weiterer Mahnschreiben in dieser Sache zu bestätigen. Hierfür habe ich mir eine Frist bis zum {{14 Tage ab Schreiben}} notiert. Andernfalls erfolgt umgehend Beschwerde bei der für Ihre Zulassung zuständigen Stelle.

Darüber hinaus fordere ich Sie außerdem auf, wie im § 19 BDSG Abs. 1 vorgesehen, mir mitzuteilen, welche Daten Sie über mich gespeichert haben, aus welcher Quelle Sie diese Daten haben und an wen Sie diese Daten weitergegeben haben. Des Weiteren erwarte ich, dass, gemäß § 20 BDSG Abs. 2, meine Daten gelöscht werden. Wenn dies aufgrund eines Gesetzes nicht möglich ist, sind die Daten zu sperren (vgl. §20 BDSG Abs. 3). Sollten Sie der Aufforderung nicht nachkommen, behalte ich mir vor die Sache beim Datenschutzbeauftragten Ihres Bundeslandes einzureichen

Quelle: https://www.antispam-ev.de/wiki/Antwortbrief_gegen_ungerechtfertigte_Inkassoforderung


Einsortiert unter:German, Misc Tagged: Datenschutz

Karsten Kempe: Prozessanpassungen in Visual Studio Team Services – Work Item Types

VSTSJetzt ist es also soweit – Visual Studio Team Services erlaubt die Erstellung eigener Work Item Typen. Ein weiterer wichtiger Schritt, um den Prozess-Workflow effektiv anpassen zu können. Ich habe mir für Euch das Vorgehen schon mal näher angesehen.

Wie immer benötigt man einen individuellen Prozess (wie man einen eigenen Prozess erzeugt, habe ich in den Basics näher beschrieben), bevor man jegliche Form von Anpassung vornehmen kann.

Innerhalb des neuen Prozesses ist nun ein Link verfügbar („New work item type“), über den man das neue Work Item erstellen kann. Ziemlich easy, denn das war alles. Keine XML Dateien exportieren oder importieren und keine Fehler mehr, weil man darin irgendwo eine Referenz vergessen hat anzupassen.

VSTS_WorkItemType_Create

Aber Achtung, so ganz optimal funktioniert das Ganze noch nicht. Ich kann das Work Item nämlich nach der Anlage nicht mehr umbenennen (noch nicht!).

Das neue Work Item kann anschließend, wie jedes Work Item, auch angepasst werden (Layout, Felder oder auch States). Neu ist ein sogenanntes Backlogs-Tab, in dem bestimmt werden kann, in welchem Backlog oder Board ein neues Work Item verwendet werden soll.

VSTS_WorkItemType_BacklogsVSTS_WorkItemType_Backlog_Entry

Eigene Work Item Typen können auch jeder Zeit wieder zerstört werden. Und mit zerstört, meine ich wirklich zerstört. Alle Work Items des Typs und alle Referenzen darauf werden entfernt. In Kürze soll es auch ein „Disable Work Item“ geben, damit bleiben alle Work Items im System, aber es können keine neuen mehr angelegt werden.

Probiert das Ganze doch mal selbst aus und gebt mir Feedback, welche Features Ihr Euch noch wünschen würdet.

Happy customizing!

Holger Schwichtenberg: Windows 10 Anniversary Update erzwingen

Mit einem kleinen Hilfsprogramm von Microsoft kann man die sofortige Aktualisierung von Windows 10 herbeiführen, auch wenn in den Einstellungen kein Update angezeigt wird.

Manfred Steyer: Lazy Loading von Modulen mit Angular 2, dem neuen Router und webpack

Um die Ladezeiten einer Single Page Application (SPA) zu optimieren, bietet es sich an, einzelne Bestandteile nicht sofort sondern erst bei Bedarf zu laden. Seit RC 5 unterstützt Angular 2 dieses Vorgehen, welches auch als Lazy Loading bekannt ist. Dazu kommen die neu eingeführten Module, mit der eine Anwendung strukturiert und in mehrere wiederverwendbare Einheiten aufgesplittet werden kann, zum Einsatz.

Mit diesem Beitrag veranschauliche ich den Umgang mit Modulen und Lazy Loading anhand eines Beispiels, welches zum Bundling webpack verwendet. Das vollständige Beispiel findet sich hier. Wer mit den hier beschriebenen Techniken ein eigenes Beispiel erstellen möchte, findet für den Start auf den GitHub-Seiten des Angular-2-Teams ein sehr einfaches Seed-Projekt, welches auch webpack nutzt.

Beispiel

Das hier verwendete Beispiel bietet über den Router die Menüpunkte Home, Login und Flug Buchen an:

Beispielanwendung

Die ersten beiden Menüpunkte stehen ab dem Start der Anwendung zur Verfügung. Die Anwendungsteile für den Menüpunkt Flug Buchen lädt die Anwendung erst bei Bedarf nach:

Flug suchen

Hierzu wurde die Anwendung auf vier Module aufgeteilt:

Modulstruktur des Beispiels

Das AppModul beinhaltet die Root-Component, welche die gesamte Anwendung repräsentiert und beim Bootstrapping von Angular 2 erzeugt wird. Solche Module nennt man deswegen auch Root-Module. Es referenziert das HomeModule mit den Komponenten für die ersten beiden Menübefehle. Daneben lädt es über den Router beim ersten Aufruf des Befehls Flug Buchen das FlugModule in die Ausführungsumgebung. Sowohl HomeModule als auch FlugModule verweisen auf das SharedModule, welches allgemeine Komponenten und Services beherbergt.

Allgemeiner AuthService im SharedModule

Das SharedModule bietet unter anderem einen AuthService an. Dieser simuliert das An- und Abmelden des Benutzers und hält dessen Namen und Stadt vor. Wie das nachfolgende Listing zeigt, handelt es sich dabei aus Gründen der Vereinfachung um eine Implementierung für äußerst ehrliche Benutzer:

@Injectable()
export class AuthService {

    isLoggedIn: boolean = false;
    userName: string = null;
    city: string = "Wien";

    login() {
        this.isLoggedIn = true;
        this.userName = "Max";
        this.city = "Graz";
    }

    logout() {
        this.isLoggedIn = false;
        this.userName = null;
        this.city = "Wien";
    }
}

Wichtig für die weitere Betrachtung des Beispiels ist, dass der Service bei einem anonymen Benutzer die Standardwert Wien als Standardwert annimmt, während der verwendete Demo-Benutzer zur Stadt Graz zugeordnet ist.

Das SharedModule importiert das CommonModule sowie das FormsModule. Ersteres beinhaltet allgemeine Direktiven, wie ngIf, ngFor oder ngStyle und letzteres fügt Unterstützung für die Arbeit mit Formularen hinzu:

@NgModule({
    imports: [
        CommonModule,
        FormsModule
    ],
    declarations: [
        OrtValidatorDirective,
        OrtAsyncValidatorDirective,
        DateComponent,
        OrtPipe
    ],
    exports: [
        OrtValidatorDirective,
        OrtAsyncValidatorDirective,
        DateComponent,
        OrtPipe
    ],
    providers: []
})
export class SharedModule {

    static forRoot(): ModuleWithProviders {
        return {
            ngModule: SharedModule,
            providers: [AuthService]
        }
    }
}

Der Abschnitt declarations legt die Inhalte des Moduls fest. Dabei kann es sich um Direktiven, Komponenten und Pipes handeln. Unter exports listet das Modul jene Inhalte, die es exportiert und somit anderen Modulen zur Verfügung stellt.

Etwas spannender gestaltet sich die Eigenschaft providers. Sie listet jene Provider, welche das Modul global einrichten soll.
Hier würde man einen Provider für den vorhin besprochenen AuthService erwarten. Da jedoch das verzögert geladene FlugModule auch auf das SharedModule zugreift, ist das keine gute Idee, denn aufgrund der Implementierung von Lazy Loading richtet Angular 2 solche Services für verzögert geladene erneut (!) ein. Unterm Strich gäbe es also zwei Instanzen des "Singletons" AuthService:

Two "Singletons"

Dieser Umstand ist kaum wünschenswert, da er unweigerlich zu Inkonsistenzen führt. Beispielsweise könnte die eine Instanz über den angemeldeten Benutzer informieren während die zweite davon ausgeht, dass dieser noch nicht bekannt ist. Für die Lösung dieses Problems gibt das Angular-Team ein Muster (eigentlich ein Idiom) vor. Demnach ist das Modul in zwei Ausprägungen anzubieten: Einmal ohne und einmal mit Provider. Per Definition stellt eine statische Methode forRoot die Ausprägung mit Provider zur Verfügung. Dazu erzeugt Sie eine Instanz von ModuleWithProviders, welche das Modul sowie die Provider gruppiert. Dieses ModuleWithProviders ist über das Root-Module, welches die Services einmalig global zur Verfügung stellt, zu referenzieren. Bei allen anderen Modulen kommt die Variante ohne Provider zum Einsatz:

forRoot

Die Auflistung providers definiert tatsächlich globale Provider, da Module keinen eigenen Injector bekommen. Um Provider mit beschränktem Wirkungsbereich zu definieren, sind diese auf der Ebene einer Komponente zu registrieren.

Definition des Feature-Modules HomeModule

Das beim Start der Anwendung zu ladende HomeModule importiert unter anderem das im letzten Abschnitt besprochene SharedModule:

@NgModule({
    imports: [
        CommonModule,
        SharedModule
    ],
    declarations: [
        HomeComponent, 
        LoginComponent
    ],
    exports: [
        HomeComponent, 
        LoginComponent
    ],
    providers: []
})
export class HomeModule {
}

Darüber hinaus exportiert es eine HomeComponent, welche als Startseite fungiert, und eine LoginComponent, die mit dem AuthService den aktuellen Benutzer an- bzw. abmeldet.

Definition des Feature-Modules FlugModule

Das verzögert geladene FlugModule kommt mit einer eigenen Routen-Konfiguration. Darin findet sich unter anderem eine Route für die Komponente FlugBuchenComponent, welche einen leeren Pfad aufweist und somit als Startseite des Moduls fungiert:

const FLUG_ROUTES =    [{
    path: '',
    component: FlugBuchenComponent,
    canActivate: [AuthGuard],
    children: [
        {
            path: 'flug-suchen', 
            component: FlugSuchenComponent
        },
        {
            path: 'flug-suchen-reactive',
            component: FlugSuchenReactiveComponent
        },
        {
            path: 'passagier-suchen',
            component: PassagierSuchenComponent
        },
        {
            path: 'flug-edit/:id',
            component: FlugEditComponent,
            canDeactivate: [FlugEditGuard]
        }
    ]
}];

Außerdem richtet diese Konfiguration ein paar Child-Routes ein und verweist auf zwei hier nicht näher betrachtete Guards, welche das Provider-Array FLUG_ROUTE_PROVIDERS einrichtet:

export const FLUG_ROUTE_PROVIDERS = [
    AuthGuard,
    FlugEditGuard
];

Da die hier betrachtete Routen-Konfiguration jene der Root-Component erweitert, ist sie an die statische Methode RouterModule.forChild zu übergeben:

export const FlugRouterModule = RouterModule.forChild(FLUG_ROUTES);

Diese Methode erzeugt für das FlugModule ein konfiguriertes RouterModule. Dabei handelt es sich abermals um ein ModuleWithProviders.

Das FlugModule erhält auch eine FlugSuchenComponent:

@Component({
    selector: 'flug-suchen',
    template: require('./flug-suchen.component.html'),
    providers: [],
    styles: [require('./flug-suchen.component.css')]
})
export class FlugSuchenComponent {

    public von: string = "";
    public nach: string = "";
    public datum: string = (new Date()).toISOString();

    public selectedFlug: Flug;

    constructor(private flugService: FlugService, private authService: AuthService) {
        this.von = authService.city;
    }

    [...]
}

Sie lässt sich den AuthService aus dem SharedModule injizieren und übernimmt die Stadt des aktuellen Benutzers in die Eigenschaft von, um sie als Abflugflughafen vorzuschlagen.

Das FlugModule referenziert unter anderem das SharedModule sowie das FlugRouterModule mit der besprochenen Routing-Konfiguration:

@NgModule({
    imports: [
        CommonModule, 
        FormsModule, 
        ReactiveFormsModule,
        SharedModule,    
        FlugRouterModule 
    ],
    declarations: [
        FlugBuchenComponent, 
        FlugCardComponent, 
        FlugSuchenComponent, 
        FlugSuchenReactiveComponent, 
        PassagierSuchenComponent, 
        FlugEditComponent
    ],
    providers: [
        FlugService, 
        FLUG_ROUTE_PROVIDERS
    ]
})
export class FlugModule {
}

Das betrachtete Modul deklariert auch die von der Routen-Konfiguration verwendeten Komponenten. Daneben richtet es einen Provider für einen FlugService ein. Zusätzlich nimmt es das Array FLUG_ROUTE_PROVIDERS mit den Guards in die providers-Auflistung auf.

Routen für Root-Module AppModule

Die Routen für das AppModule verweisen auf die HomeComponent sowie auf die LoginComponent. Die Route flug-buchen verweist auf keine Komponente. Stattdessen gibt sie über loadChildren einen Lambda-Ausdruck an, welcher das FlugModule bei Bedarf nachlädt:

export const ROUTE_CONFIG: Routes = [
    {
        path: '',
        redirectTo: 'home',
        pathMatch: 'full'
    },
    {
        path: 'home',
        component: HomeComponent
    },
    {
        path: 'login',
        component: LoginComponent
    },
    {
        path: 'flug-buchen',
        loadChildren: () => System.import('./modules/flug/flug.module').then(m => m.FlugModule)
    },
    {
        path: '**',
        redirectTo: 'home'
    }
];

Die Anweisung System.import veranlasst webpack2 das Bundle an dieser Stelle zu splitten. Auf diese Weise ergibt sich für den referenzierten Anwendungsteil ein eigenes Bundle, welches nach der Nomiklatur von webpack auch als Chunk bezeichnet wird. Zur Laufzeit lädt System.import diesen Chunk bei Bedarf nach.

Als dieser Text verfasst wurde, war webpack2 noch in der BETA-Phase und Version 1.x die aktuell stabile. Diese nutzt zum Definieren weiterer Chunks die Methode require.ensure, welche ähnlich wie System.import funktioniertm jedoch Callbacks anstatt von Promises einsetzt. Da loadChildren vorsieht, dass der übergebene Lambda-Ausdruck das nachgelade Modul über einen Promise liefert, ist der Aufruf dieser Methode in einem solchen Promise zu kapseln:

{
    path: 'flug-buchen',
    loadChildren: () => new Promise((resolve) => {
        (require as any).ensure([], (require: any) => {
            resolve(require('./modules/flug/flug.module').FlugModule);
        })
    })
},

Webpack nutzt die Aufrufe von require.ensure nicht nur zur Laufzeit für das Nachladen sondern auch beim Bundling als Markierungspunkte für Chunk-Grenzen. Damit beim Bundling diese Chunk-Grenzen klar identifizierbar sind, muss die Anwendung require.ensure sowie das geschachtelte require mit fixen Werten aufrufen. Dies verhindert leider ein Verstecken des hier gezeigten etwas spärlichen Aufrufs in einer Hilfsmethode.

Der Vollständigkeit halber zeigt das nachfolgende Listing die Schreibweise, die beim Einsatz des Module-Loaders SystemJS zum Einsatz kommt. Dabei wird lediglich ein String mit dem Namen der Datei, welche das nachzuladende Modul beheimatet, und dem Modulnamen anzugeben:

{
    path: 'flug-buchen',
    loadChildren: 'app/modules/flug/flug.module#FlugModule'
}

Um auch für diese Konfiguration ein konfiguriertes RouterModule zu erhalten, ist sie an RouterModule.forRoot zu übergeben:

export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG);

Im Gegensatz zu forChild kommt forRoot für Routen im Root-Module zum Einsatz. Das auf diese Weise erhaltene AppRoutesModule wird exportiert.

Per Konvention kommen forRoot-Methoden NUR in der Root-Module zum Einsatz!

Definition des Root-Modules AppModule

Das AppModule deklariert die AppComponent, welche als Root-Component zum Einsatz kommt, und importiert das vorhin besprochene Modul AppRoutesModule mit der Routen-Konfiguration:

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        HttpModule,
        FormsModule,
        ReactiveFormsModule,
        AppRoutesModule,
        HomeModule,
        SharedModule.forRoot()
    ],
    providers: [
        { provide: "BASE_URL", useValue: "http://www.angular.at" }
    ],
    bootstrap: [
        AppComponent 
    ]
})
export class AppModule { 
}

Dabei ist zu beachten, dass das SharedModule über seine statische Methode forRoot einzubinden ist, zumal es sich hierbei um das Root-Module handelt.

Bundling mit Webpack

Beim Bundling mit Webpack wird nun für das FlugModule ein eigener Chunk erzeugt. Da dafür kein Name festgelegt wurde, handelt es sich um einen unbenannten Chunk, welchen webpack mit einer fortlaufenden Zahlen benennt. Webpack 1 startet dabei mit 1; Webpack 2 mit 0:

>webpack
Hash: a16aebe9bab18c48aa57
Version: webpack 2.1.0-beta.21
Time: 13539ms
        Asset     Size  Chunks             Chunk Names
         0.js   562 kB       0  [emitted]
       app.js   280 kB       1  [emitted]  app
     tests.js  5.74 kB       2  [emitted]  tests
    vendor.js  2.77 MB       3  [emitted]  vendor
     0.js.map   683 kB       0  [emitted]
   app.js.map   268 kB       1  [emitted]  app
 tests.js.map  6.98 kB       2  [emitted]  tests
vendor.js.map  2.78 MB       3  [emitted]  vendor
 [418] ./app spec\.ts$ 160 bytes {2} [built]
 [722] multi tests 28 bytes {2} [built]
 [723] multi vendor 40 bytes {3} [built]
    + 1029 hidden modules

Test des Lazy-Loadings

Um den Einsatz von Lazy Loading zu kontrollieren, kann der Netzwerktraffic betrachtet werden. Dazu bietet sich unter anderem das Registerblatt Network in den Developer-Tools von Chrome an. Funktioniert alles wie gewollt, wird der Chunk mit dem FlugModule (hier 0.js) erst beim Wechsel in den Menüpunkt Flug buchen angefordert:

Verzögertes Laden über Dev-Tools kontrollieren

Test des über SharedModule geteilten AuthService

Um zu prüfen, ob der AuthService des SharedModuls trotz Lazy Loading nur ein einziges Mal instanziiert wird, sollte sich der Benutzer zunächst anmelden:

Nach Login

Wird anschließend im nachgeladenen FlugModule die Stadt Graz vorgeschlagen, kann davon ausgegangen werden, dass lediglich ein globaler AuthServices existiert. Der Grund dafür ist, dass der AuthService nach dem Anmelden einen Benutzer aus Graz simuliert (siehe Listing zu AuthService, oben):

Flug suchen

Experiment mit globalen Services

Zum Erforschen der Problematik mit den mehrfachen Singletons beim Einsatz von Lazy Loading könnte man zu Testzwecken - und nur zu Testzwecken - dem FlugModule das SharedModule samt Provider spendieren. Dazu kommt ein weiteres Mal die Methode forRoot zum Einsatz:

@NgModule({                     //            _  _
    imports: [                  //      ___ (~ )( ~)
        CommonModule,           //     /   \_\ \/ /
        RouterModule,           //    |   D_ ]\ \/
        FormsModule,            //    |   D _]/\ \
        ReactiveFormsModule,    //     \___/ / /\ \
        FlugRouterModule,       //          (_ )( _)
        // Alt: SharedModule    //
        SharedModule.forRoot()  // <-- Böse!!!! Nur zum Test!!!
    ],
    declarations: [
        FlugBuchenComponent, 
        FlugCardComponent, 
        FlugSuchenComponent, 
        FlugSuchenReactiveComponent, 
        PassagierSuchenComponent, 
        FlugEditComponent
    ],
    providers: [
        FlugService, 
        FLUG_ROUTE_PROVIDERS 
    ]
})
export class FlugModule {
}

Das Ergebnis sollte nun jenes unerwünschte aus der oben diskutierten Abbildung sein: FlugModule hat seinen eigenen AuthService. Da das Anmelden des Benutzers mit der Login-Komponente im Module HomeModule erfolgt, kennt dieser AuthService innerhalb des FlugModules diesen Benutzer nicht. Er geht davon aus, dass der Benutzer nie angemeldet wurde und schlägt somit beim Flugsuchen immer den Wert Wien vor. Wie eingangs anhand der Implementierung gezeigt, handelt es sich dabei um den Standardwert für anonyme Benutzer.

Nach diesem Experiment sollte der Eintrag SharedModule.forRoot() wieder durch SharedModule ersetzt werden, damit sich das System wie gewünscht verhält.

Zusammenfassung

Ab RC 5 kann eine Angular-2-Anwendung in mehrere wiederverwendbare Module unterteilt werden. Um die Startzeit zu optimieren, bietet der Router die Möglichkeit, diese Module erst bei Bedarf zu laden (Lazy Loading). Damit das funktioniert müssen die nachzuladenden Module in einem eigenen Bundle platziert werden. Webpack kümmert sich darum, wenn die Anwendung das Modul über require.ensure angefordert. Etwas mehr Komfort bietet webpack2, welches stattdessen die Promise-basierte Methode System.import unterstützt.

Besondere Aufmerksamkeit benötigen Services, welche in mehreren separat geladenen Modulen zum Einsatz kommen. In diesem Fall darf der Service nur auf der Ebene des Root-Modules registriert werden, da Angular 2 ansonsten den "Singleton" mehrfach einrichtet. Der konsequente Einsatz des hierfür vom Angular-2-Team vorgeschlagenen Musters hilft, solche Fälle zu erkennen und adäquat zu behandeln.

codefest.at [MS]: Xamarin Webcast Serie

Wie starten ab/am 14. September eine dreiteilige Webcast Serie (eng.) zu Xamarin:

clip_image002

September 14, 2016 - 11:00-12:00
1: Introduction to Xamarin mobile development

The first webcast will focus on the challenges on developing for multiple mobile platforms and how Xamarin enables developers to do this quicker and more efficiently. In the second part of the webcast we will look at how this development is done using Visual Studio or Xamarin Studio.

September 28, 2016 – 11:00-12:00:
2: Building Cross Platform Mobile Applications using Xamarin

October 12, 2016 – 11:00-12:00:
3: Mobile Dev Ops powered by Xamarin and Microsoft

Sven Hubert: ALM kompakt: Copy and Publish Build Artifacts vs Copy Files und Publish Build Artifacts

Wer schon einmal in seinem Build verschiedene Dateien kopieren und als Artifakte ablegen wollte, der kommt an den Tasks Copy Files und Publish Build Artifacts nicht vorbei. Wer beide Tasks kombinieren möchte kann Copy and Publish Build Artifacts verwenden.

Sofern jemand seine Dateien nicht kopieren möchte, um diese vor dem Publish noch anzupassen, lassen sich beide ähnlich anwenden. Es kann ein Quellverzeichnis ausgewählt und mehrere Dateien über ein Minimatch-Pattern selektiert werden. Anschließend lässt sich ein Artifaktname und der Artifakttyp angeben. Doch wo genau unterscheiden sich diese nun?

Auf die Lösung kommt man, wenn man versucht Dateien zu exkludieren. Bei einem Minimatch-Pattern (siehe minimatch) kann über ein führendes Anführungszeichen eine Anweisung negiert werden. Dadurch lassen sich z.B. mit !**\node_modules\** sämtliche npm-Pakete aus den Artifakten exkludieren. Versucht man dies jedoch mit dem Copy and Publish Build Artifacts Task, so wird der Ordner nicht exkludiert, sondern trotzdem mitkopiert. Wie kann das sein?

Die Lösung findet sich im Source Code der Build Tasks wieder. Hier zeigt sich, das Copy and Publish Build Artifacts nicht auf die bereits bestehenden Tasks Copy Files und Publish Build Artifacts verweist, sondern eine eigene Implementierung besitzt. Anbei ist der Unterschied zwischen Copy and Publish Build Artifacts und Copy Files beim Matchen der Dateien dargestellt:

copypublishartifactUnterschied: Copy and Publish Build Artifacts und Copy Files

Wer auf Zeile 101-104 in Copy Files schaut, sieht, dass hier die Negierung von Patterns berücksichtigt wurde, bei Copy and Publish Build Artifacts jedoch nicht. Auf der MSDN-Seite der Copy and Publish Build Artifacts Task wurde dazu folgender Beitrag veröffentlicht:

imageMSDN FAQ zu Copy and Publish Build Artifacts

Interessanterweise gibt es nicht nur unterschiedliche Implementierung in den Microsoft Tasks, sondern auch unterschiedliche Suchmuster für Dateien. Z.B. bei den Visual Studio Test Tasks kann bei der Test Assembly ebenfalls ein Suchmuster eingeben werden . Hier sieht ein exkludieren von Test Assemblies wie folgt aus: **\*Test*.dll;-:**\obj\**

Die Zeichenkombination -: exkludiert alle Dateien, die dem nachfolgenden Suchmuster entsprechen. Hierbei handelt es sich nicht um ein Minimatch-Pattern. Das zeigt sich auch, wenn man die Implementierung der Task betrachtet (siehe unten). Für diese wird die PowerShell Funktion Find-Files verwendet, welche ein eigenes Pattern besitzt. Wenn man sich die JavaScript-Implementierung der Task anschaut, so ist dieses Extra-Pattern auch dort implementiert.

testpatternImplementierung der Visual Studio Test Tasks

Als Fazit ziehen wir somit: Man kann sich nie sicher sein, dass zusammengeführte Tasks auf vorherige Implementierungen verweisen oder gleich implementiert wurden. Dies lässt sich u.a. beim Exkludieren von Dateien oder auch bei Suchmustern beobachten. Hier hilft notfalls nur ein Blick in die Sourcen der Tasks. Unser Tipp: Wer diese nicht vom TFS bekommt, kann für die Microsoft Tasks auch auf GitHub schauen.

Uli Armbruster: Gitolite Berechtigungssystem

In diesem Video gebe ich einen Einblick in das Berechtigungssystem von Gitolite. Ich spreche über Zugriffsgruppen, über das Vererbungssystem, über Wild Repos, über mögliche Einsatzszenarien und einiges mehr. Eine ausführliche Dokumentation findet ihr hier.

Die 2 im Video genannten Befehle waren

  1. Prüfung der Berechtigungen für ein Repository (zur Doku): Im Beispiel wird geprüft, ob der User ‚uli.armbruster‘ Berechtigung zum Löschen des ‚master‘-Branch im Repository ‚co-it/homepage‘ hat.gitolite access -s co-it/homepage uli.armbruster D refs/heads/master 
  2. Übersteuern von Berechtigungen am Repository selbst: In dem Beispiel würde mir Gregor auf sein userspezifisches-Repository ‚users/gregor.woiwode/spike1‘ Lesezugriff geben.ssh git@ci.heco.de perms users/gregor.woiwode/spike1 + READERS uli.armbruster

 

Bei Fragen einfach nutzt die Kommentarfunktion oder kontaktiert mich direkt.


Einsortiert unter:German Tagged: Git, Video

Uli Armbruster: Gewährleistung und das Übel mit der Beweislastumkehr

Disclaimer
Dieser Beitrag entspricht meiner privaten Recherche und darf nicht als rechtlich verbindend angesehen werden. Es soll eher als Einstieg in das Thema dienen, um einen groben Überblick zu kriegen.

Abgrenzung Garantie und Gewährleistung
Zuerst einmal ist zu beachten, dass Gewährleistung nicht gleich Garantie ist. Viele denken, dass es sich bei den Begriffen um das Gleiche handelt, jedoch ist die Garantie nur eine freiwillige Leistung des Verkäufers, zusätzlich zur gesetzlich geltenden Gewährleistungspflicht.
„Mit der Garantie sichert der Verkäufer eine unbedingte Schadensersatzleistung zu, während die Gewährleistung eine zeitlich befristete Nachbesserungspflicht bei Mängeln beschreibt.“

Gewährleistungszeit
Normalerweise gilt die Gewährleistung 2 Jahre ab Übergabe der Kaufsache, jedoch kann die Frist auch verkürzt bzw. sogar ausgeschlossen werden. Zu unterscheiden ist v.a. zw. Neuware und gebrauchter Ware und zw. Unternehmer und Privatpersonen.

Neuware

Unternehmer verkauft an Unternehmer Gewährleistung kann auf 1 Jahr verkürzt werden
Unternehmer verkauft an Privatperson 2 Jahre, keine Verkürzung möglich
Privatperson verkauft an Privatperson Gewährleistung kann auf 1 Jahr beschränkt werden


gebrauchte Ware

Unternehmer verkauft an Unternehmer Gewährleistung kann ausgeschlossen werden
Unternehmer verkauft an Privatperson Gewährleistung kann auf 1 Jahr verkürzt werden
Privatperson verkauft an Privatperson Gewährleistung kann ausgeschlossen werden

 

Beweislastumkehr
Beim Verbrauchsgüterkauf (Privatperson ist der Käufer, ein Unternehmen ist Verkäufer) ist zu beachten, dass der Verkäufer in den ersten 6 Monaten im Falle eines Mangels beweisen muss, dass dieser nicht schon beim Verkauf vorlag. Nach den 6 Monaten tritt die Beweislastumkehr in Kraft und der Käufer muss beweisen, dass der Mangel bereits beim Verkauf vorhanden war. Obwohl Privatpersonen also rechtlich 2 Jahre Gewährleistung zustehen, wird es nach 6 Monaten schwierig diese in Anspruch zu nehmen. Denn um den benötigten Nachweis zu erbringen, bedarf es in der Regel ein spezielles Gutachten.

Handelt es sich bei dem Käufer um ein Unternehmen, so wird davon ausgegangen, dass man sich mit dem Kauf von Waren auskennt und deshalb die Ware direkt begutachten und etwaige Fehler gleich reklamieren kann. Sprich hier gilt die Beweislastumkehr sofort bzw. spätestens nach 14 Tagen.

Bei Verkäufen unter Privatpersonen liegt die Beweislast immer beim Käufer.

Fazit
Die Gewährleistungspflicht hilft Privatpersonen nur in den ersten 6 Monaten. Besser ist es beim Kauf auf eine möglichst lange Garantie seitens des Herstellers zu achten. Persönlich habe ich außerdem die Erfahrung gemacht, dass ein entsprechend kulanter Verkäufer wie Amazon in solchen Fällen sehr hilfreich sein kann. Mit Cyberport habe ich hingegen sehr schlechte Erfahrungen gemacht.

Quellen


Einsortiert unter:German, Misc

Fabian Deitelhoff: Sehenswert + Verlosung: Let’s Code Minecraft

Bei dieser Verlosung geht es um das Video-Training Let’s Code Minecraft von Tom Wendel und Patrick Kirsch. Ein weiteres gute Beispiel dafür, wie sich das Programmieren Lernen mit einem spielerischen Ansatz, hier Minecraft, verbinden lässt.

Das Video-Training im Überblick

Let’s Code Minecraft – Programmieren lernen mit Minecraft

Rheinwerk, 02/2016, 70 Videos, 11 Stunden Spielzeit
ISBN: 978-3-8362-4056-7, 29,90 Euro

Sprache: Deutsch
Ausstattung: DVD
Download: Auch als Download verfügbar
Probekapitel: Blicke ins Training
Gesamtnote: sehr gut – 4,5 von 5 Sternen
Empfehlung: Ja

Let's Code Minecraft - Programmieren lernen mit Minecraft (Rheinwerk, 2016)

Der Inhalt

Beim Programmieren Lernen, im Zusammenhang mit Minecraft, geht es ganz grob darum, hilfreiche Roboter und Computer zu programmieren. Diese sollen in der Lage sein, Tätigkeiten innerhalb der Minecraft-Welt zu erledigen. Zum Beispiel Ressourcen abbauen oder Konstruktionen errichten. Denn das kann in Minecraft auf Dauer recht mühsam werden. Und wiederkehrende Aufgaben mit einem Programm zu vereinfachen ist ein guter Ansatz.

Das Video-Training ist dabei in acht Kapitel aufgeteilt:

  1. Entdecke die Möglichkeiten von Minecraft
  2. Programmieren leicht gemacht
  3. Computertechnik mit Redstone-Erzen
  4. Roboterspaß im Kleinformat: Die ComputerCraft-Turtles
  5. Die Turtle als intelligenter Suchroboter
  6. Den Hausumzug planen und vorbereiten
  7. Der Turtle-Umzugshelfer in Aktion
  8. Programmieren in der echten Welt

Neben einer kurzen Einführung in Minecraft und einiger Tätigkeiten darin, geht es auch direkt los. Redstone-Schaltungen sind die erste Konstrukte, mit denen im Training programmiert beziehungsweise gesteuert wird.

Dieses Training ist ebenfalls, verglichen mit dem Training zur Spieleprogrammierung, sehr gut gestaltet. Die Software ist übersichtlich, erlaubt es auch einzelne Kapitel anzuspringen und ermöglicht es so, vielleicht schwierige Abschnitte noch einmal durchzugehen. Die technische Umsetzung des Trainings ist daher mal wieder vorbildlich.

Fazit & Bewertung

Aber auch der Inhalt ist erstklassig und die beiden Sprecher Tom Wendel und Patrick Kirsch machen einen hervorragenden Job. An der ein oder anderen Stelle hätte ich mir ein paar kleinere Beispiele gewünscht, die einzelne Aspekte hervorheben und losgelöst von dem großen begleitenden Beispiel erklären. Das macht es evtl. für Anfänger etwas einfacher, dem Inhalt zu folgen. Komplette Anfänger im Bereich der Programmierung können hier evtl. ins Stocken geraten.

Alles in allen aber ein super Video-Training und eine absolute Empfehlung meinerseits.

Verlosung

Und wieder einige obligatorische Informationen zur Verlosung. Das Video-Training wurde mir freundlicherweise vom Rheinwerk-Verlag zur Verfügung gestellt. Angesehen habe ich es einmal. Die Verpackung des Trainings ist in einem sehr guten Zustand.

Das Training wird von mir verschickt. Alle Kosten übernehme selbstverständlich ich. Bis auf eine Adresse brauche ich dann auch nichts weiter. Dazu melde ich mich allerdings noch mal beim Gewinner beziehungsweise der Gewinnerin per E-Mail, die beim Kommentar mit angegeben werden muss. Die E-Mail Adresse ist natürlich nur von mir einsehbar.

Wer gewinnen möchte, hinterlässt bitte einen Kommentar, warum er oder sie das Video-Training gerne hätte. Das muss nichts Besonderes sein. Ich möchte nur gerne vermeiden, dass Kommentare auftauchen, die offensichtlich dadurch entstanden sind, das einmal von links nach rechts mit dem Gesicht über die Tastatur gerollt wurde. 🙂

Aus allen Kommentaren fische ich dann per Zufall einen heraus, so wie ich das bei den letzten Verlosungen auch gemacht habe. Die Verlosung läuft von heute, Mittwoch den 31. August 2016 bis zum Freitag, den 09. September um 12:00 Uhr. Die Teilnahme ist ab 18 Jahren möglich und der Rechtsweg ist wie üblich ausgeschlossen.

Bei Fragen zum Video-Training oder sonstigen Angelegenheiten zur Verlosung freue ich mich immer über Kommentare und Nachrichten.

Vielen Glück allen!

Die Ziehung

Mittlerweile ist die Verlosung beendet. Wie immer habe ich, durch das folgende SQL-Statement, einen Kommentar herausgepickt.

SELECT 
      distinct comment_author, comment_author_email, comment_content
FROM
      wp_comments
INNER JOIN
      wp_posts
ON
      comment_post_ID = id
WHERE
      post_title = 'Sehenswert + Verlosung: Let\'s Code Minecraft'
AND
      comment_approved = 1
AND 
      user_id = 0
AND
      comment_date
BETWEEN 
      '2016-08-31 01:00:00'
AND 
      '2016-09-09 12:00:00'
ORDER BY 
      RAND()
LIMIT
      1

Herausgekommen ist folgender Kommentar. Die E-Mail Adresse habe ich unkenntlich gemacht.

Ziehung Gewinner: Let's Code Minecraft

Herzlichen Glückwunsch an den Gewinner Stefan. Ich habe gerade eine E-Mail rausgeschickt, da ich noch die Lieferadresse brauche.

Und natürlich gibt es auch in Zukunft weitere Verlosungen.

Sven Hubert: Keine Angst vor Veränderung!

Tester, die sich mit automatisierten Oberflächentests auseinandersetzen, kennen die Problematik: Oberflächen werden modernisiert und die dafür erstellten Tests lassen sich nicht mehr ausführen, da einzelne Elemente oder ganze Oberflächen neu angeordnet wurden. Nun stellt sich die Frage, ob automatisierte Oberflächentests überhaupt sinnvoll für sich stark wandelnde Oberflächen realisierbar sind?

Die Antwort auf diese Frage finden Sie im Artikel Keine Angst vor Veränderung – Stabile automatisierte Oberflächentests trotz Redesign, welcher kürzlich bei Informatik Aktuell erschienen ist.

Sven Hubert: HoloLens: Platt oder Plastisch?

Wenn man an Apps für die HoloLens denkt, dann stellt man sich in erster Linie 3D-Apps mit Hologrammen vor – diese sind natürlich auch die ganz klare Stärke der HoloLens. Doch sie kann noch mehr: alle UWP-Apps (Universal Windows Platform) können mit der HoloLens genutzt werden. Die gängigen „platten“ Apps integrieren sich als flache „Fenster“ oder „Kacheln“ im dreidimensionalen Raum. Die HoloLens-spezifischen „plastischen“ Apps können wirkliche 3D-Hologramme im Raum darstellen. Was sind die Unterschiede und auf was sollte generell bei diesen beiden App-Typen für die HoloLens geachtet werden? Wie kann man beide Typen in einer App vereinen? Dieser Teil der Blogserie soll diese und mehr Fragen zu den App-Typen beantworten.

hololens_holograms_app

Wenn man sich mit den App-Typen für die HoloLens beschäftigt, so sollte man ebenfalls Kenntnisse über das App Model der HoloLens haben. Es gibt hierzu wichtige Details zu wissen, welche beim Design von Apps berücksichtigt werden sollten.

Generell kann immer nur eine einzige App in der HoloLens aktiv sein. Sobald eine zweite App gestartet oder fortgesetzt wird, wird erstere angehalten. Man kann jedoch explizit Hintergrundprozesse definieren und anlegen. Diese laufen trotz angehaltener App weiter und ermöglichen es beispielsweise, die Audiowiedergabe fortzusetzen.

Jede App wird in der HoloLens als Kachel dargestellt. Wird die Kachel geschlossen, werden auch die Prozesse der App beendet. Bewegt man sich in der „Mixed World“ der 2D-Apps, kann man mehrere Kacheln setzen und hat damit quasi mehrere Apps auf einen Blick parat – allerdings nur mit einem statischen Splash Screen, Live Tiles werden aktuell nicht unterstützt.

hololens_mixed_worldBildquelle

Sobald jedoch eine 3D-App gestartet wird, verschwinden alle 2D-Apps der Mixed World und der gesamte Raum wird von der 3D-App eingenommen. Hier gilt also: entweder 2D oder 3D.

hololens_holographic_worldBildquelle

Generell sollte beim Design und der Entwicklung von HoloLens-Apps darauf geachtet werden, dass für die Interaktion keine Tastatur verfügbar ist. „Normale“ 2D-Apps, welche dies voraussetzen, sind also ungeeignet für die HoloLens. Jedoch kann für die HoloLens dieselbe Engine für Spracheingabe genutzt werden, welche auch für alle „normalen“ UWP-Apps bereitsteht. Für 3D-Projekte, welche mit Unity erstellt werden, bietet die Unity Engine sogar vereinfachte Methoden für die Spracherkennung, welche auch in den Unity-C#-Skripten bereitstehen – unter der Haube werden aber dieselben Bibliotheken verwendet. Durch geeignete Sprachbefehle kann das Benutzererlebnis erheblich verbessert werden.

Ein weiterer Punkt, der beachtet werden sollte, ist die Darstellung von Farben. Hologramme werden durch Hinzufügen von Licht erzeugt und da Schwarz – einfach gesagt – das Fehlen von Licht ist, kann die HoloLens kein Schwarz darstellen. In den beiden Screenshots unten ist eine App-Kachel mit einem schwarzen Streifen im unteren Teil zu sehen. Im originalen Screenshot links sieht die Darstellung korrekt aus. Dies spiegelt allerdings nicht das wirkliche Erlebnis mit der HoloLens wider. Da die Gläser der HoloLens generell abdunkeln, erscheint die schwarze Fläche im unteren Teil der App-Kachel fast im selben Farbton wie die weiße Wand neben der Kachel und wirkt dadurch für den Nutzer durchsichtig. Dafür erscheint das Rot heller als im XAML-Designer von Visual Studio. Zum Vergleich wurde der rechte Screenshot bearbeitet, um annähernd das wirkliche Bild wiederzugeben. Letztendlich muss man in jedem Fall eine App mit der HoloLens tatsächlich testen, hierfür reicht der Emulator nicht aus.

hololens_black_areasScreenshot im Original. hololens_black_areas_editedBearbeiteter Screenshot für wirklichkeitsgetreuere Darstellung.

Der Button im obigen Screenshot deutet den letzten jedoch durchaus interessanten Punkt an: Können hybride 3D/2D-Apps entwickelt werden? Die klare Antwort ist: Jein. Grundsätzlich ist es zwar möglich, dass eine App 3D-Objekte und normale 2D-Oberflächen im XAML-Design anzeigen kann, allerdings niemals zur gleichen Zeit. Selbst für eine simple Tastatur muss von der „Holographic World“ komplett zur „Mixed World“ gewechselt werden. Dies ist zumindest der Teil, den das anfangs erwähnte App-Model uns verrät. Mit den angebotenen Methoden hat dies in einem ersten Test auch funktioniert, wenn auch recht holprig.

Interessant ist allerdings, dass die für die HoloLens entwickelte App „Holograms“ aus dem Store ein anderes Verhalten zeigt. Hier gibt es eine 2D-Kachel, aus welcher Hologramme ausgewählt und im Raum platziert werden können. Dabei werden die 3D-Hologramme gleichzeitig mit den 2D-Kacheln angezeigt, wie ganz oben im ersten Bild dieses Beitrags zu sehen ist.

Das Thema hybride Apps scheint also noch nicht ganz ausgereift zu sein bzw. Microsoft muss erst die benötigten APIs den Entwicklern bereitstellen. Viele interessante Anwendungsfälle sind jedoch bereits heute denkbar. Doch schon jetzt kann das Know-how der bisherigen XAML-Entwicklung für UWP-Apps auch für die HoloLens genutzt werden.

Wollen Sie mehr über HoloLens erfahren, dann sprechen Sie uns an oder kommen Sie am 14.09.2016 zu uns nach Stuttgart auf’s Holodeck.

Johannes Renatus: React “Hello World” mit Visual Studio 2015 ASP.NET MVC 4 (jsx und tsx)

Als heißer Verfechter von AngularJs, habe ich mir gedacht schaust du auch mal über den Tellerrand und liest dich mal ein wenig in React ein. Denn bei React handelt es sich nicht um ein “komplettes” Framework wie AngularJs sondern eher um die Möglichkeit Performantere DOM Manipulation auszuführen, bei z.B. großen Listen, wo AngularJs so seine […]

Holger Schwichtenberg: Veranstaltungen für .NET- und Visual-Studio-Entwickler im Herbst 2016

Auch in diesem Herbst gibt es in Deutschland wieder einige Entwicklerkonferenzen für Softwareentwickler, die mit Microsoft-Werkzeugen wie Visual Studio und Visual Studio Code entwickeln.

Johannes Renatus: AngularJs TypeScript Interceptor Template

AngularJs bietet ein interessantes Konzept um AJAX Requests bzw. Response zu manipulieren oder zu prüfen ob ein Response bestimmte Daten enthält. Das ganze nennt sich HTTP Interceptor. Ich verwende es z.B. für die folgenden Aufgaben in meinen Projekten Prüfen ob im Response eine Meldung enthalten ist die angezeigt werden soll, wenn diese vorhanden ist, existiert […]

Sven Hubert: Qualität als Konzept: Qualitätsmetriken in Visual Studio

Um Qualität in Software zu erreichen, muss diese auch messbar sein. Doch wie lässt sich das am einfachsten machen? Was viele hier vergessen, ist, dass bereits Visual Studio die Anzeige von Qualitätsmetriken anbietet. In diesem Teil der Blog-Serie soll gezeigt werden, wie man mit Visual Studio Qualitätsmetriken anzeigt, was diese bedeuten und wie man diese auch automatisiert prüfen kann.

Codemetriken

Visual Studio bietet die Möglichkeit, Codemetriken zu berechnen, und das schon seit Version 2008. Was früher nur in der VS Premium Version vorhanden war, gibt es heute schon mit VS 2015 Community. Codemetriken können einfach für eine ganze Solution oder für ausgewählte Projekte berechnet werden. Die Option hierfür findet man unter Analyse –> Calculate Code Metrics.

image
Visual Studio: Codemetriken berechnen

Das Ergebnis der Berechnung wird dann in einem separaten Fenster ausgegeben. In diesem kann man fünf Qualitätsmetriken sehen: Maintainability Index (MI), Cyclomatic Complexity (CC), Depth of Inheritance (DoI), Class Coupling (CP) und Lines Of Code (LOC). Jeder dieser Metriken wird pro Projekt, Namespace, Klasse und Methode berechnet. Die Ergebnisse können nach Wertebereich zu einer Codemetrik gefiltert werden. Zusätzlich gibt es noch einen Export nach Excel. Nützlich ist auch das direkte Anlegen eines Work Items aus den Ergebnissen heraus. Dabei wird für die selektierte Zeile ein Work Item angelegt, welches das analysierte Projekt, den Scope (Namespace, Klasse, Methode), die Konfiguration (Debug, Release) und die einzelnen Codemetriken automatisch hinterlegt. Dieses Work Item kann dann dazu verwendet werden, um ein Refactoring einzuplanen, welches diese Codemetriken verbessert.

image
Visual Studio: Ergebnis der Codemetriken

Cyclomatic Complexity
Die Cyclomatic Complexity zeigt, wie viele Ausführungspfade es gibt. Je höher die Anzahl Pfade, desto komplexer das Projekt. Je mehr Pfade es gibt, desto höher ist auch die Anzahl an Tests, die diese Pfade abdecken. Die Anzahl Pfade kann nicht unbedingt verringert werden, aber ausgelagert in andere Methoden oder Klassen. Dadurch gibt es zwar mehr Methoden, jedoch sind diese von der Komplexität einfacher zu verstehen.

Depth of Inheritance
Die Depth of Inheritance gibt an, wie tief die Hierarchie einer abgeleiteten Klasse ist. Dabei ist der Standardwert einer Klasse immer eins. Für den Namespace und das Projekt wird jeweils der maximale Wert über alle Klassen verwendet. Je tiefer die Hierarchie einer Klasse, desto komplexer ist diese, bietet auf der anderen Seite jedoch auch eine bessere Wiederverwertbarkeit. Bei der Berechnung der Depth of Inheritance werden nur Klassen und keine Interfaces betrachtet.

Class Coupling
Class Coupling beschreibt, wie viele andere Klassen innerhalb einer Methode oder Klasse verwendet werden. Anhand der verwendeten Klassen kann man erkennen, ob eine Klasse zu viele Abhängigkeiten besitzt, die aufgelöst werden müssen. Dadurch erkennt man auch, ob eine Klasse zu viel Verantwortung hat, die in mehrere Klassen geteilt werden sollte. Bei der Berechnung von Class Coupling gilt zu beachten, dass es hier bestimmte Ausnahmen gibt, wie z.B. gewisse Value Types (string, int, double), die nicht zur Metrik hinzugenommen werden. Das bedeutet jedoch nicht, dass struct’s nicht zum Class Coupling dazugezählt werden.

Lines Of Code
Die Lines of Code können darauf hindeuten, dass ein Projekt, eine Klasse oder eine Methode zu groß ist und aufgeteilt werden sollte. Wie lang zu lange ist, da scheiden sich die Geister. Ein guter Richtwert ist jedoch 100 LOC pro Methode und 1000 LOC pro Klasse als Maximum. Innerhalb der Codemetriken sind die LOC nicht die wirklichen Zeilen C#-Code, sondern vielmehr die Zeilen IL-Code die beim Kompilieren erzeugt werden. Das hat den Vorteil, das zusätzliche Leerzeilen oder Formatierung über mehrere Zeilen keine Auswirkung auf die LOC haben. Am Ende zählen immer nur die Anzahl an IL-Operationen.

Maintainability Index
Der Maintainability Index wird berechnet aus der Halstead Volume, der Cyclomatic Complexity und den Lines Of Code.  Genauer kann hier nachgelesen werden, wie sich diese Metrik berechnet. Die Metrik beschreibt, wie einfach es ist, den Code zu warten. Der Maintainability Index hat einen Wert zwischen 0 und 100, je höher desto besser. Zwischen 20 und 100 ist der Farbindikator grün, zwischen 10 und 19 gelb und zwischen 0 und 9 rot. Als Richtwert sollte der Maintainability Index jedoch nie die 50 unterschreiten.

Wer die Metriken direkt im Code sehen möchte und eine Visual Studio Edition mit CodeLens-Support besitzt, dem wird mit der Extension Microsoft CodeLens Code Health Indicator geholfen. Diese zeigt direkt als CodeLens-Erweiterung die aktuellen Metriken direkt bei der Methode oder Klasse innerhalb des Code-Editors an. Das ist hilfreich, damit man direkt bei der Programmierung erkennt, wann ein Refactoring angebracht ist.

Codeanalyse

Zum kontinuierlichen und auch automatischen Prüfen der Codemetriken, ohne diese immer manuell berechnen und nachschauen zu müssen, gibt es vier Codeanalyseregeln, die genau diese abbilden. Diese Regeln haben eine feste Grenze eingebaut, welche wenn überschritten oder unterschritten, eine Warnung generieren. Die Cyclomatic Complexity darf 25 auf Methodenebene nicht überschreiten. Die Depth of Inheritance darf nicht größer sein als 4. Das Class Coupling darf auf Klassenebene 80 und auf Methodenebene 30 nicht überschreiten. Der Maintainability Index muss größer sein als 19 (grün).

image
Codeanalyse: Codemetrikregeln

Da Codeanalysis eher reaktiv ist und Probleme erst aufzeigt, wenn sie schon vorhanden sind, sollte natürlich kontinuierlich geprüft werden, wie sich die Qualitätsmetriken entwickeln, um hier proaktiv zu sein. Da die Codemetriken auch während eines automatischen Builds über das Commandline-Tool der Code Metrics berechnet und als XML exportiert werden können, ist es möglich, diese auch während des automatischen Builds zu prüfen. Hier können dann auch eigene Grenzwerte gewählt werden, um früher auf mögliche Probleme hingewiesen zu werden.

Fazit

Wem diese fünf Codemetriken ausreichen, der kann ohne großen Aufwand diese in seinen Qualitätsprozess integrieren. Ein wesentlicher Teil ist dabei, die Metriken proaktiv statt reaktiv zu überprüfen und eventuelle Probleme direkt zu beheben. Gleichzeitig heißt es noch lange nicht, dass wenn diese fünf Codemetriken in Ordnung sind, dann auch die Qualität des kompletten Projekts passt. Dies ist lediglich einer von vielen Indikatoren, wie eine mangelhafte Codequalität im Vorfeld erkannt und verbessert werden kann.

Sven Hubert: HoloLens: App unter Source Control

Bei einer HoloLens App handelt es sich zum Teil um ein Unity Projekt und zum Teil um ein Visual Studio Projekt. Diese gehören genau wie bei anderen Projekten unter Source Control. Doch was genau muss eingecheckt werden? In diesem Teil der Blog-Serie soll geklärt werden, wie ein HoloLens-Projekt aufgebaut ist und was davon eingecheckt werden muss.

Ordnerstruktur

Um die Ordnerstruktur zu erklären, nehmen wir uns als Beispiel den Galaxy Explorer von Microsoft. Gehostet auf GitHub besitzt er bereits eine gitignore-Datei, um irrelevante Dateien herauszufiltern. Interessant ist dies, da wir hier einen direkten Vergleich haben, wie sich die Ordnerstruktur verändert, sobald wir das Projekt zum ersten Mal öffnen und builden. Für den Build wurde in Unity als Platform Windows Store ausgewählt mit dem Universal 10 SDK und D3D als UWP Build Type. Der Buildoutput wurde in den Ordner App nach Microsoft-Vorbild geschrieben.


galaxyexplorer_builtGalaxy Explorer: Ordnerstruktur vor und nach dem erstem BuildGalaxy Explorer: Ordnerstruktur vor und nach dem erstem Build

App / UWP

Der App-Ordner wird beim Build in Unity angelegt. Innerhalb dieses Ordners liegen alle Dateien, um das Unity-Projekt mit Visual Studio als Marketplace UWP-App zu kompilieren. In der darunter liegenden Visual Studio Solution können somit keine Anpassungen mehr an den Assets wie z.B. den C#-Skripten gemacht werden. Dies geschieht direkt in Unity oder in der separat dafür angelegten Visual Studio Solution im Hauptordner.
Da alles innerhalb des App-Ordner beim Build generiert wird, sollte dies auch nicht mit in die Source Control. Wer seine UWP-App für den Windows Store konfigurieren möchte, findet alle Einstellungen der Package.appxmanifest ebenfalls in Unity (Build Settings –> Windows Store –> Player Settings).
Der UWP-Ordner ist ein Nebenprodukt und beinhaltet die NuGet-Paketkonfiguration der UWP-App.

Assets

Im Assets-Ordnet befindet sich alle Assets, die in Unity über den Asset-Manager erstellt und hinzugefügt werden können. Dies beinhalten z.B. alle 3D-Modelle, Shader oder C#-Skripte. Der Assets Ordner ist zentral für das Unity-Projekt und beinhaltet jeglichen Content einer HoloLens-App. Aus diesem Grund sollte dieser auch eingecheckt werden. Das schöne daran ist, dass die Visual Studio Projekte und auch das Unity Projekt auf die gleichen Assets verweisen. Somit können die C#-Skripte in Visual Studio angepasst werden, während Unity die Änderungen mitbekommt.

Library / obj / Temp

Der Library-Ordner wird von Unity generiert und befüllt. Dieser beinhaltet vorkompilierte Assets wie z.B. Shader oder C#-Skripte und dient somit als eine Art File Cache. Den obj-Ordner kennen viele aus dem Visual Studio Umfeld. Dieser beinhaltet die Binaries für die kompilierten C#-Skripte. Bei dem Ordner Temp spricht der Name schon für sich. Alle drei Ordner sollten nicht eingecheckt werden.

ProjectSettings

Alle Einstellungen im Unity Projekt werden im ProjectSettings-Ordner abgespeichert. Dabei wird als Format YAML (Yet another markup language) verwendet, wodurch ein Vergleich von Änderungen an den Projekteinstellungen einfach möglich ist.

Dadurch ergeben sich zwei Ordner, die in die Source Control eingecheckt werden sollten: Assets und ProjectSettings.

Ignore-Dateien

Das GitHub-Projekt gitignore gibt eine erste Anlaufstelle, denn hier werden für unterschiedliche Technologien und Editoren gitignore-Dateien gesammelt, die alle irrelevanten Dateien der entsprechenden Projekte herausfiltern. Hier gibt es bereits für Unity und auch für Visual Studio eine gitignore-Datei. Da sich die Syntax zwischen gitignore für Git und tfignore für den TFS nicht sonderlich unterscheidet, kann man auch gitignore-Dateien für den TFS verwenden.
Die Visual Studio gitignore-Datei ist sehr ausführlich, wobei das meiste nicht benötigt wird. Hier reicht es aus, den App-Ordner und den UWP-Ordner zu ignorieren. Wer auf der Suche nach einem guten Zwischenweg ist, der kann auch die gitignore-Datei des Galaxy Explorer verwenden. Diese findet man im GitHub-Projekt hier.

Wollen Sie mehr über HoloLens erfahren, dann sprechen Sie uns an oder kommen Sie am 14.09.2016 zu uns nach Stuttgart auf’s Holodeck.

Stefan Henneken: IEC 61131-3: Das ‘Command’ Pattern

Durch den Aufruf einer Methode kann an einem Funktionsblock ein Befehl ausgeführt werden. Funktionsblock A ruft eine Methode von Funktionsblock B auf. So weit so gut. Doch wie lässt sich der Austausch solcher “Befehle” zwischen mehreren Funktionsblöcken flexibel gestalten? Das Command Pattern liefert hier einen interessanten Ansatz.

Ein kleines Beispiel aus der Heimautomation soll uns hierbei behilflich sein. Angenommen wir haben mehrere FBs die jeweils ein Gerät bzw. Aktor repräsentieren. Jedes Gerät hat einen individuellen Satz an Befehlen, welche unterschiedliche Funktionen zur Verfügung stellen.

Pic01

Ein weiterer Funktionsblock soll ein 8fach-Tastenfeld abbilden. Dieses Tastenfeld enthält 8 Taster, die den einzelnen Funktionalitäten (Befehlen) der Geräte zugeordnet werden. Durch eine positive Flanke an den jeweiligen Eingang werden die notwendigen Befehle bei den Geräten aufgerufen.

FUNCTION_BLOCK PUBLIC FB_SwitchPanel
VAR_INPUT
  arrSwitch           : ARRAY[1..8] OF BOOL;
END_VAR
VAR
  fbLamp              : FB_Lamp;
  fbSocket            : FB_Socket;
  fbAirConditioning   : FB_AirConditioning;	
  fbCDPlayer          : FB_CDPlayer;
  arrRtrig            : ARRAY[1..8] OF R_TRIG;
END_VAR

arrRtrig[1](CLK := arrSwitch[1]);
IF arrRtrig[1].Q THEN
  fbSocket.On();
END_IF

arrRtrig[2](CLK := arrSwitch[2]);
IF arrRtrig[2].Q THEN
  fbSocket.Off();
END_IF

arrRtrig[3](CLK := arrSwitch[3]);
IF arrRtrig[3].Q THEN
  fbLamp.SetLevel(100);
END_IF

arrRtrig[4](CLK := arrSwitch[4]);
IF arrRtrig[4].Q THEN
  fbLamp.SetLevel(0);		
END_IF

arrRtrig[5](CLK := arrSwitch[5]);
IF arrRtrig[5].Q THEN
  fbAirConditioning.Activate();
  fbAirConditioning.SetTemperature(20.0);
END_IF

arrRtrig[6](CLK := arrSwitch[6]);
IF arrRtrig[6].Q THEN
  fbAirConditioning.Activate();
  fbAirConditioning.SetTemperature(17.5);
END_IF

arrRtrig[7](CLK := arrSwitch[7]);
IF arrRtrig[7].Q THEN
  fbCDPlayer.SetVolume(40);
  fbCDPlayer.SetTrack(1);
  fbCDPlayer.Start();	
END_IF

arrRtrig[8](CLK := arrSwitch[8]);
IF arrRtrig[8].Q THEN
  fbCDPlayer.Stop();	
END_IF

In Punkto Flexibilität ist dieser Entwurf eher suboptimal. Warum?

Unflexibel. Es besteht eine feste Zuordnung zwischen FB_SwitchPanel und den einzelnen Geräten (FB_Lamp, FB_Socket, FB_CDPlayer und FB_AirConditioning). Soll z.B. FB_Socket durch ein zweites FB_Lamp ersetzt werden, so ist es notwendig die Implementierung von FB_SwitchPanel anzupassen.

Fehlende Wiederverwendbarkeit. Sollen auch die Taster 3 und 4 zum Ansteuern des CD-Players dienen, so muss die notwendige Abfolge von Methodenaufrufen erneut programmiert werden.

Undynamisch. Das 8fach-Tastenfeld wurde als Funktionsblock implementiert. Solange alle Tastenfelder die gleiche Tastenbelegung haben, ist dieser Ansatz lauffähig. Was aber, wenn die Tastenbelegung unterschiedlich sein soll? Entweder müsste für jede Tastenbelegung ein individueller Funktionsblock programmiert werden oder es müssen Programme statt Funktionsblöcke zum Einsatz kommen.

Definition des Command Pattern

Die Lösung des Problems liegt in der Einführung einer Softwareschicht, die sich zwischen dem Tastenfeld und den Geräten einfügt. Diese Schicht kapselt jeden einzelnen Befehl (mit einem Command-FB) und enthält alle relevanten Methodenaufrufe um eine Aktion an einem Gerät auszuführen. Der 8fach-Taster sieht somit nur noch diese Befehle und enthält keine weiteren Referenzen zu den jeweiligen Geräten.

Das Command Pattern definiert drei Schichten:

Invoker FBs dieser Schicht lösen an den Command-FBs den gewünschten Befehl aus. Der Aufrufer (Invoker), in unseren Beispiel das 8fach-Tastenfeld (FB_SwitchPanel), kennt nicht die Empfänger der Befehle. Es weiß aber, wie ein Befehl gestartet wird.
Receiver Dieses sind die FBs, die die jeweiligen Empfänger (Receiver) der Befehle repräsentieren; also FB_Socket, FB_Lamp, …
Commands Jeder Befehl wird durch einen FB abgebildet. Dieser FB enthält eine Referenz auf dem Empfänger. Des Weiteren besitzen diese Befehle eine Methode um den Befehl zu aktivieren. Wird diese Methode aufgerufen, so ist dem Command-FB bekannt, welche Methoden am Empfänger ausgeführt werden müssen, um den gewünschten Effekt zu erzielen.

Schauen wir uns die Command-FBs genauer an.

Ein Command-FB kapselt einen “Auftrag”, indem es einen Satz von Aktionen für einen bestimmten Empfänger enthält. Hierzu werden die Aktionen und die Referenz beim Empfänger zu einem FB zusammengefasst. Über eine Methode (z.B. Execute()) sorgt der Command-FB dafür, das am Empfänger die richtigen Aktionen ausgeführt werden. Der Aufrufer sieht von Außen nicht, welche Aktionen das genau sind. Dieser weiß nur, wenn er die Methode Invoke() aufruft, dass alle notwendigen Schritte ausgeführt werden.

Commands

Hier die Implementierung für den Command-FB um an FB_Socket den On-Befehl auszuführen:

FUNCTION_BLOCK PUBLIC FB_SocketOnCommand
VAR
  refSocket : REFERENCE TO FB_Socket;
END_VAR

Die Variable refSocket enthält die Referenz auf eine Instanz des Bausteins FB_Socket, also den Empfänger des Befehls. Gesetzt wird diese Referenz über die Methode FB_init.

METHOD FB_init : BOOL
VAR_INPUT
  bInitRetains : BOOL;
  bInCopyCode : BOOL;
  refNewSocket : REFERENCE TO FB_Socket;
END_VAR

IF (__ISVALIDREF(refNewSocket)) THEN
  THIS^.refSocket REF= refNewSocket;
ELSE
  THIS^.refSocket REF= 0;
END_IF

Die Methode Execute() führt die notwendige Aktion an FB_Socket aus:

METHOD Execute
IF (__ISVALIDREF(THIS^.refSocket)) THEN
  THIS^.refSocket.On();
END_IF

Je nach Empfänger kann diese Methode aber auch aus mehreren Aktionen bestehen. So sind die folgenden Methodenaufrufe notwendig, um das Abspielen einer CD zu starten:

METHOD Execute
IF (__ISVALIDREF(THIS^.refCDPlayer)) THEN
  THIS^.refCDPlayer.SetVolume(40);
  THIS^.refCDPlayer.SetTrack(1);
  THIS^.refCDPlayer.Start();
END_IF

Da in unserem Bespiel alle Command-FBs die Methode Execute() zum Ausführen des Befehls anbieten, wird diese Methode durch das Interface I_Command vereinheitlicht. Jeder Command-FB muss dieses Interface implementieren.

Invoker

Dem 8fach-Tastenfeld (FB_SwitchPanel) muss jetzt nur noch mitgeteilt werden, welche Command-FBs es nutzen soll. Die Details der Command-FBs müssen nicht bekannt sein. FB_SwitchPanel kennt nur 8 Variablen, die vom Typ I_Command sind. Wird eine positive Flanke an einen der Taster erkannt, so wird über das Interface I_Commnd von dem Command-FB die Methode Invoke() aufgerufen.

FUNCTION_BLOCK PUBLIC FB_SwitchPanel
VAR_INPUT
  arrSwitch    : ARRAY[1..8] OF BOOL;
END_VAR
VAR
  aiCommand    : ARRAY[1..8] OF I_Command;	
  arrRtrig     : ARRAY[1..8] OF R_TRIG;
  nIndex       : INT;
END_VAR

FOR nIndex := 1 TO 8 DO
  arrRtrig[nIndex](CLK := arrSwitch[nIndex]);
  IF arrRtrig[nIndex].Q THEN
    IF (aiCommand[nIndex] <> 0) THEN
      aiCommand[nIndex].Execute();
    END_IF
  END_IF
END_FOR

Zuvor wird durch die Methode SetCommand() das gewünschte Command-FB den einzelnen Tastern zugeordnet. Dadurch ist FB_SwitchPanel universal einsetzbar.

METHOD PUBLIC SetCommand : BOOL
VAR_INPUT
  nPosition   : INT;
  iCommand    : I_Command;
END_VAR

IF ((nPosition >= 1) AND (nPosition <= 8) AND (iCommand <> 0)) THEN
  THIS^.aiCommand[nPosition] := iCommand;
END_IF

Der Invoker FB_SwitchPanel kennt nicht den Empfänger (Receiver). Es sieht nur 8mal das Interface I_Command mit seiner Methode Execute().

Receiver

An den FBs, die einen Empfänger abbilden, brauchen keine Anpassungen durchgeführt werden. Über entsprechende Methoden oder Eingänge kann ein Command-FB die notwendigen Aktionen ausführen.

Anwendung

Hier ein kleines Beispielprogramm, das aus den drei oben gezeigten Softwareschichten eine Anwendung zusammenstellt:

PROGRAM MAIN
VAR
  // Invoker
  fbSwitchPanel                 : FB_SwitchPanel;
	
  // Receiver
  fbSocket                      : FB_Socket;
  refSocket                     : REFERENCE TO FB_Socket := fbSocket;	
  fbLamp                        : FB_Lamp;
  refLamp                       : REFERENCE TO FB_Lamp := fbLamp;	
  fbAirConditioning             : FB_AirConditioning;
  refAirConditioning            : REFERENCE TO FB_AirConditioning := fbAirConditioning;
  fbCDPlayer                    : FB_CDPlayer;
  refCDPlayer                   : REFERENCE TO FB_CDPlayer := fbCDPlayer;

  // Commands
  fbSocketOnCommand             : FB_SocketOnCommand(refSocket);
  fbSocketOffCommand            : FB_SocketOffCommand(refSocket);
  fbLampSetLevel100Command      : FB_LampSetLevelCommand(refLamp, 100);
  fbLampSetLevel0Command        : FB_LampSetLevelCommand(refLamp, 0);
  fbAirConComfortCommand        : FB_AirConComfortCommand(refAirConditioning);
  fbAirConStandbyCommand        : FB_AirConStandbyCommand(refAirConditioning);	
  fbMusicPlayCommand            : FB_MusicPlayCommand(refCDPlayer);
  fbMusicStopCommand            : FB_MusicStopCommand(refCDPlayer);

  bInit                         : BOOL;
END_VAR

IF (NOT bInit) THEN
  fbSwitchPanel.SetCommand(1, fbSocketOnCommand);
  fbSwitchPanel.SetCommand(2, fbSocketOffCommand);
  fbSwitchPanel.SetCommand(3, fbLampSetLevel100Command);
  fbSwitchPanel.SetCommand(4, fbLampSetLevel0Command);
  fbSwitchPanel.SetCommand(5, fbAirConComfortCommand);
  fbSwitchPanel.SetCommand(6, fbAirConStandbyCommand);
  fbSwitchPanel.SetCommand(7, fbMusicPlayCommand);
  fbSwitchPanel.SetCommand(8, fbMusicStopCommand);
  bInit := TRUE;
ELSE
  fbSwitchPanel();	
END_IF

Pro 8fach-Taster (Invoker) wird eine Instanz von FB_SwitchPanel angelegt.

Von jedem Gerät (Receiver) wird ebenfalls je eine Instanz deklariert. Des Weiteren wird von jeder Instanz noch eine Referenz benötigt.

Die erzeugten Referenzen werden bei der Deklaration der Command-FBs (Commands) mit übergeben (an FB_init). Falls notwendig, können hier noch weitere Parameter übergeben werden. So besitzt das Kommando zum Setzen der Beleuchtung noch einen Parameter für die gewünschte Stellgröße.

In diesem Beispiel können mit der Methode SetCommand() die einzelnen Command-FBs den 8 Tastern zugeordnet werden. Die Methode erwartet als ersten Parameter die Tastennummer (1…8) und als zweiten Parameter einen FB, der das Interface I_Command implementiert.

Die erreichten Vorteile sind recht überzeugend:

Entkopplung. Sender (Invoker) und Empfänger (Receiver) sind voneinander entkoppelt. Dadurch kann FB_SwitchPanel generisch entworfen werden. Durch die Methode SetCommand() besteht die Möglichkeit die Zuordnung der Taster zur Laufzeit anzupassen.

Erweiterbarkeit. Es können beliebige Command-FBs hinzugefügt werden. Selbst wenn FB_SwitchPanel durch eine Bibliothek zur Verfügung gestellt wird, kann ein Programmierer beliebige Command-FBs erstellen und mit FB_SwitchPanel verwenden, ohne das Anpassungen an der Bibliothek notwendig werden. Dadurch, dass die zusätzlichen Command-FBs das Interface I_Command implementieren, können diese von FB_SwitchPanel genutzt werden.

Beispiel 1 (TwinCAT 3.1.4020)

UML-Klassendiagramm

 

UML

Das Interface I_Command wird von allen Kommandos implementiert.

Der Invoker besitzt über das Interface I_Command Verweise auf Commands und führt diese bei Bedarf aus.

Ein Command ruft alle notwendigen Methoden des Receivers auf.

MAIN stellt die Verbindungen zwischen den einzelnen Commands und dem Receiver her.

Erweiterungen

Das Command Pattern lässt sich sehr einfach um weitere Funktionen erweitern.

Makro-Befehle

Besonders interessant ist die Möglichkeit, die Befehle beliebig miteinander zu kombinieren und in einem neuen Befehlsobjekt zu kapseln. Man spricht hierbei von sogenannten Makro-Befehlen.

Ein Makro-Befehl hält ein Array von Befehlen. In diesem Beispiel können bis zu vier Command-FBs hinterlegt werden. Da jeder Command-FB das Interface I_Command implementiert, können die Befehle in ein Array vom Typ ARRAY[1..4] OF I_Command gespeichert werden.

FUNCTION_BLOCK PUBLIC FB_RoomOffCommand IMPLEMENTS I_Command
VAR
  aiCommands    : ARRAY[1..4] OF I_Command;
END_VAR

Über die Methode FB_init() werden die einzelnen Command-FBs an den Makro-Befehl übergeben.

METHOD FB_init : BOOL
VAR_INPUT
  bInitRetains : BOOL;
  bInCopyCode : BOOL;
  iCommand01 : I_Command;
  iCommand02 : I_Command;
  iCommand03 : I_Command;
  iCommand04 : I_Command;
END_VAR

THIS^.aiCommand[1] := 0;
THIS^.aiCommand[2] := 0;
THIS^.aiCommand[3] := 0;
THIS^.aiCommand[4] := 0;
IF (iCommand01 <> 0) THEN
  THIS^.aiCommand[1] := iCommand01;
END_IF
IF (iCommand02 <> 0) THEN
  THIS^.aiCommand[2] := iCommand02;
END_IF
IF (iCommand03 <> 0) THEN
  THIS^.aiCommand[3] := iCommand03;
END_IF
IF (iCommand04 <> 0) THEN
  THIS^.aiCommand[4] := iCommand04;
END_IF

Bei Ausführung der Methode Execute() wird über das Array iteriert und bei jedem Befehl Execute() aufgerufen. Somit können mit einem einzigen Execute() des Makro-Befehls gleich mehrere Befehle auf einmal ausgeführt werden.

METHOD Execute
VAR
  nIndex  : INT;
END_VAR

FOR nIndex := 1 TO 4 DO
  IF (THIS^.aiCommands[1] <> 0) THEN
    THIS^.aiCommands[1].Execute();
  END_IF
END_FOR

In MAIN werden bei der Deklaration des Makro-Befehls die 4 Command-FBs übergeben. Da der Makro-Befehl selbst ein Command-FB ist (er implementiert das Interface I_Command), kann dieser bei dem 8fach-Tasten einem Taster zugeordnet werden.

PROGRAM MAIN
VAR
  ...
  fbRoomOffCommand     : FB_RoomOffCommand(fbSocketOffCommand,
                                           fbLampSetLevel0Command,
                                           fbAirConStandbyCommand,
                                           fbMusicStopCommand);
  ...
END_VAR

IF (NOT bInit) THEN
  ...
  fbSwitchPanel.SetCommand(8, fbRoomOffCommand);
  ...
ELSE
  fbSwitchPanel();	
END_IF

Vorstellbar wäre auch eine Methode, welche zur Laufzeit dem Makro-Befehl die einzelnen Command-FBs übergibt. Die Implementierung wäre vergleichbar mit der Methode SetCommand() vom 8fach-Tastenfeld. Somit könnte z.B. ein Szenenbaustein realisiert werden, bei dem der Anwender die Befehle über eine Bedieneroberfläche selbst einer Szene zuordnen kann.

Beispiel 2 (TwinCAT 3.1.4020)

Undo-Funktionalität

Ein weiteres mögliches Feature ist die eine Rückgängigkeits-Funktion. Das 8fach-Tastenfeld erhält einen weiteren Eingang, mit dem der zuletzt ausgeführte Befehl rückgängig gemacht wird. Dazu wird das Interface I_Command um die Methode Undo() erweitert.

UML2

Diese Methode enthält die Umkehrung der Execute-Methode. Wird mit der Execute-Methode die Steckdose eingeschaltet, so wird im gleichen Command-FB mit der Undo-Methode diese wieder ausgeschaltet.

FUNCTION_BLOCK PUBLIC FB_SocketOffCommand IMPLEMENTS I_Command
VAR
  refSocket : REFERENCE TO FB_Socket;
END_VAR

METHOD Execute
IF (__ISVALIDREF(THIS^.refSocket)) THEN
  THIS^.refSocket.Off();
END_IF

METHOD Undo
IF (__ISVALIDREF(THIS^.refSocket)) THEN
  THIS^.refSocket.On();
END_IF

Etwas aufwendiger ist die Implementierung der Undo-Methode für das Setzen der Lampenhelligkeit. In diesen Fall muss der Command-FB um ein “Gedächnis” erweitert werden. Hier wird vor dem Setzen einer neuen Stellgröße, durch die Methode Execute(), die vorherige Stellgröße abgelegt. Die Undo-Methode verwendet beim Aufruf diesen Wert, um die vorherige Stellgröße wiederherzustellen.

FUNCTION_BLOCK PUBLIC FB_LampSetLevelCommand IMPLEMENTS I_Command
VAR
  refLamp        : REFERENCE TO FB_Lamp;
  byNewLevel     : BYTE;
  byLastLevel    : BYTE := 255;
END_VAR

METHOD Execute
IF (__ISVALIDREF(THIS^.refLamp)) THEN
  THIS^.byLastLevel := THIS^.refLamp.Level;	
  THIS^.refLamp.SetLevel(THIS^.byNewLevel);
END_IF

METHOD Undo
IF (__ISVALIDREF(THIS^.refLamp)) THEN
  IF (THIS^.byLastLevel <> 255) THEN
    THIS^.refLamp.SetLevel(THIS^.byLastLevel);
  END_IF
END_IF

Nachdem die Command-FBs um eine Undo-Methode erweitert wurden, muss auch das 8fach-Tastenfeld angepasst werden.

FUNCTION_BLOCK PUBLIC FB_SwitchPanel
VAR_INPUT
  bUndo         : BOOL;
  arrSwitch     : ARRAY[1..8] OF BOOL;
END_VAR
VAR
  aiCommand     : ARRAY[1..8] OF I_Command;	
  arrRtrig      : ARRAY[1..8] OF R_TRIG;
  iLastCommand  : I_Command;
  fbRtrigUndo   : R_TRIG;
  nIndex        : INT;
END_VAR

FOR nIndex := 1 TO 8 DO
  arrRtrig[nIndex](CLK := arrSwitch[nIndex]);
  IF arrRtrig[nIndex].Q THEN
    IF (aiCommand[nIndex] <> 0) THEN
      aiCommand[nIndex].Execute();
      iLastCommand := aiCommand[nIndex];
    END_IF
  END_IF
END_FOR

fbRtrigUndo(CLK := bUndo);
IF fbRtrigUndo.Q THEN
  IF (iLastCommand <> 0) THEN
    iLastCommand.Undo();
  END_IF
END_IF

In Zeile 19 wird der zuletzt ausgeführte Befehl zwischengespeichert. Bei der Aktivierung der Undo-Funktion wird auf diesen Befehl zurückgegriffen und die Undo-Methode ausgeführt (Zeile 27). Die Details für die Umkehrung eines Befehls sind im Command-FB implementiert. Das 8fach-Tastenfeld greift nur über das Interface I_Command auf die einzelnen Befehle zu.

Beispiel 3 (TwinCAT 3.1.4020)

Befehle protokollieren

Da jeder Command-FB das Interface I_Command implementiert, kann jeder Befehl in eine Variable vom Typ I_Command gespeichert werden. Hiervon wurde z.B. bei der Undo-Funktionalität Gebrauch gemacht. Wird diese Variable durch einen Puffer ersetzt, so erhalten wir die Möglichkeit, Befehle zu protokollieren. Die Analyse und die Diagnose einer Anlage kann dadurch erleichtert werden.

Zusammenfassung

Der zentrale Gedanke des Command Pattern ist das Entkoppeln von Invoker und Receiver durch Befehlsobjekte.

  • Ein Entwickler kann neue Command-FBs hinzufügen, ohne das der Code am Invoker (8fach-Taster) geändert werden muss.
  • Die Zuweisung der Befehle an den Invoker kann dynamisch zur Laufzeit erfolgen.
  • Command-FBs können an verschiedenen Stellen wiederverwendet werden. Coderedundanz wird hierdurch vermieden.
  • Befehle können “intelligent” gemacht werden. Somit lassen sich z.B. Makrobefehle und Undo-Befehle realisieren.

Code-Inside Blog: TFS 2015: Adding a new Windows Build Agent

The TFS 2015 Build System

The build system before TFS 2015 was based on a pretty arcane XAML workflow engine which was manageable, but not fun to use. With TFS 2015 a new build system was implemented, which behave pretty much the same way as other build systems (e.g. TeamCity or AppVeyor).

The “build workflow” is based on a simple “task”-concept.

There are many related topics in the TFS world, e.g. Release-Management, but this blogpost will just focus on the “Getting the system ready”-part.

TFS Build Agents

Like the other parts of Microsoft the TFS is now also in the cross-platform business. The build system in TFS 2015 is capable of building a huge range of languages. All you need is a compatible build agent.

My (simple) goal was to build a .NET application on a Windows build agent via the new TFS 2015 build system.

Step 1: Adding a new build agent

Important - Download Agent.

This one is maybe the hardest part. Instead of a huge TFS-Agent-Installer.msi you need to navigate inside the TFS control panel to the “Agent pool”-tab.

You need at least one pool and need to click the “Download Agent” button.

Step 2: Configure the agent

Configuration.

The .zip package contains the actual build agent executable and a .cmd file.

Invoke the “ConfigureAgent.cmd”-file:

We run those agents as Windows Service (which was one of the last config-questions) and are pretty happy with the system.

Step 3: You are done

Now your new build agent should appear under the given build agent pool:

TFS Build Agents.

After googleing around I also found the corresponding TFS HowTo, which describes more or less the complete setup. Well… now it is documented on MSDN and this blog. Maybe this will help my future-self ;)

Karsten Kempe: Was ist Visual Studio Team Services?!

Ein neues Video zu Visual Studio Team Services ist erschienen. Besser hätte ich VSTS auch nicht erklären können. Unbedingt ansehen!

Sven Hubert: ALM Kompakt: Versionieren von Assemblies im Build-Prozess

Um welche Version der Software handelt es sich denn?

Diese Frage wird spätestens dann gestellt, wenn die Software den Entwickler-PC verlässt und von Endanwendern getestet wird. Beim Reproduzieren und fixen von Fehlern ist die Angabe einer Verionsnummer und die Rückverfolgbarkeit zu dem passenden Quellcode-Stand eine zentrale Information. Doch wie werden Versionsnummern richtig vergeben?

Aufbau der Versionsnummern

Die klassische Versionsnummer untergliedert sich in vier Stellen: <Major>.<Minor>.<Build>.<Rev>: Die Haupt- bzw. Nebenversionsnummer des Produktes (Major und Minor Version) werden in der Major und Minor Stelle gespeichert und klassischerweise vom Produkt Management oder der Marketing-Abteilung vorgegeben. Die Buildnummer wird in der Stelle Build gespeichert. In der vierten Stelle (Rev) wird die Revisionsnummer gespeichert.

Mögliche Versionierung

Wichtig bei der Versionierung ist die Generierung einer streng monoton wachsenden und dadurch eindeutigen Versionsnummer. Weiterhin ist zu beachten, dass Installer nur die Major, Minor und Build Stelle einer Datei überprüfen, um zu bestimmen, ob eine Datei bei einem Update ersetzt werden muss.

Die Major und Minor Stelle der Versionsnummer des Produkts sollte von außen vorgegeben werden (Zum Beispiel über Parameter in der Build Definition). Die Build Stelle der Versionsnummer sollte auf das Datum des aktuellen Builds gesetzt werden. Zusammen mit dem in der Revisionsstelle gespeicherten Build des Tages ergibt sich eine monoton wachsende Versionsnummer.

Diese Vergabe der Versionsnummer stellt jedoch nur eine Möglichkeit dar. Eine weitere Variante wäre die semantische Versionierung. Auf die Vor- und Nachteile der verschiedenen Varianten wird an dieser Stelle jedoch nicht weiter eingegangen, da der Fokus auf der Integration in den Buildprozess liegt.

Setzen von Versionsnummern

Die Assembly- und File-Version wird grundsätzlich in der AssemblyInfo.cs festgelegt. Je nach Aufbau der Software kann so für jede Assembly eine separate Version, oder durch die Nutzung einer GlobalAssemblyInfo.cs eine gemeinsame Version, vergeben werden. Das Vergeben der Versionsnummer sollte Grundsätzlich im Buildprozess geschehen, um lokale Builds eindeutig von Server-Builds unterscheiden zu können. Das Grundprinzip besteht nun darin, eine Buildnummer zu erzeugen, welche die Versionsnummer wiederspiegelt. Basierend auf der Buildnummer kann in einem Build-Task die aktuelle Versionsnummer durch die Buildnummer ersetzt werden.

Für die Major- und Minor-Version wird, um sie manuell vergeben zu können, eine Variable im Buildprozess definiert. Die Werte für <Build> und <Rev> werden über bereits vorhandene Variablen im Buildprozess zusammengesetzt. Das Zusammensetzen der <Build>-Stelle kann aus dem Jahr und der Tagesnummer erfolgen. Der Wertebereich von 65535 wird somit erst im Jahr 2066 überschritten. Die resultierende Buildnummer ist nachfolgend dargestellt.

image

$(MajorVersion).$(MinorVersion).$(Year:yy)$(DayOfYear)$(Rev:.rr)

Um die Versionsnummer in der AssemblyInfo patchen zu können, ist ein zusätzlicher Schritt in der Builddefinition nötig. Auf dem Visual Studio Marketplace existiert hierfür eine Extension mit dem passenden Build-Task. Durch die Extension Colin’s ALM Corner Build & Release Tools steht nach der Installation der Build-Task Version Assemblies zur Verfügung.

image

Diese Variante ist jedoch erst ab dem TFS 2015 Update 2 verfügbar. Das Anpassen der Inhalte der AssemblyInfo ist jedoch auch einfach über ein PowerShell-Skript möglich.

# Regular expression pattern to find the version in the build number 
# and then apply it to the assemblies 
$VersionRegex = "\d+\.\d+\.\d+\.\d+"

# Get and validate the version data 
$VersionData = [regex]::matches($Env:BUILD_BUILDNUMBER,$VersionRegex) 
$NewVersion = $VersionData[0]

# Apply the version to the assembly property files 
$files = gci $Env:BUILD_SOURCESDIRECTORY -recurse | 
    ?{ $_.PSIsContainer } | 
    foreach { gci -Path $_.FullName -Recurse -include GlobalAssemblyInfo.* }

foreach ($file in $files) { 
    $filecontent = Get-Content($file) 
    attrib $file -r 
    $filecontent -replace $VersionRegex, $NewVersion | Out-File $file 
    Write-Warning "$file.FullName - version applied" 
}

Das komplette Skript steht hier zum Download zur Verfügung. Je nach Anwendungsfall ist der Regex-Ausdruck und der Filter für die AssemblyInfo-Datei anzupassen. Das Skript muss anschließend nur noch in der Versionsverwaltung abgelegt, und in der Builddefinition aufgerufen werden.

image

Somit wird jede Assembly im Buildprozess mit einer eindeutigen Versionsnummer versehen und kann jederzeit zum Build und zum Quellcode zurückverfolgt werden.

image

Sven Hubert: HoloLens: Erstkontakt mit der Hardware

Wie Sie beim Lesen unserer letzten Blogbeiträge mitbekommen haben, sind wir stolze Besitzer zweier Microsoft HoloLenses. Mit und ohne die Brillen sind wir bereits eifrig dabei, erste Erfahrungen zu sammeln und unseren Kunden diese Eindrücke vorzustellen (so wie z.B. auf der DWX 2016). Im vorherigen Blogartikel ging es um die Software-Plattform und die Entwicklungswerkzeuge für dieses neuartige Gerät. Dieser Teil der Blog-Serie gibt Ihnen einen Eindruck über die technischen Details und die Hardware, also die 3D-Brille selbst.

01_HoloLense

Die HoloLens (Bildquelle)

 

Was ist in der HoloLens-Box?

Die HoloLens kommt in einem edlen runden Soft-Case mit Reißverschluss, in dem sich die Augmented-Reality-Brille selbst und weiteres hilfreiches Zubehör befinden. Zum Lieferumfang gehören noch ein kleines Handbuch, der HoloLens-Klicker (eine Art Fernbedienung), ein Micro-USB-Kabel mit Netzstecker, eine weitere Nasenauflage und ein Überkopfband. Schon beim ersten Anfassen/Herausholen spürt man das relativ hohe Gewicht der Brille von ca. 600g, die später zum Teil auf der eigenen Nase liegen werden.

 

02_HoloLensBox

HoloLens-Box mit Inhalt

 

Das erste Mal die HoloLens auf dem Haupt

Als Erstes folgen wir dem Handbuch und befestigen das Überkopfband, stellen uns die Größe des Bands für unseren Kopf entsprechend ein. Befestigt wird dann die HoloLens über ein Rädchen am beweglichen Kopfband, welches direkt mit der Brille verbunden ist. Spüren wir beim Aufziehen das Gewicht der HoloLens auf unserer Nase zu stark, so sollte man das Überkopfband nochmals nachjustieren. Es empfiehlt sich auch die zweite Nasenauflage auszuprobieren. Gemäß Handbuch sollte das Kopfband hinten etwas nach unten verschoben werden für einen besseren Halt. Meine persönlichen Versuche zeigten jedoch, dass der Tragekomfort mit einem eher geraden Kopfband wesentlich angenehmer ist (auch zeigen einige Microsoft-Videos/Fotos die gleiche Tragehaltung). Ist alles richtig justiert, so spürt man die HoloLens kaum noch auf der Nase. Das Gewicht wird komplett vom Kopf getragen. Die Brille selbst sitzt fest direkt vor den Augen und integriert sich zum größten Teil in das Sichtfeld (auch als Brillenträger war dies gut möglich).

 

03_HandbuchBild

Tragehaltung gemäß Handbuch

04HoloLensOnHead

Angenehmere Tragehaltung (Bildquelle)

 

Das erste Mal die erweiterte Realität auf der Nase

Einmal die HoloLens aufgezogen und auf den Träger justiert, drücken wir den Start-Knopf, der einfach erreichbar am hinteren Ende der Brille liegt. Beim Starten der HoloLens erscheint ein begrüßendes „Hello“ und das Microsoft-Windows-Symbol zusammen mit dem typischen Windows Start-Ton. Das HoloLens-eigene Betriebssystem „Windows Holographic“ wird gestartet und nach dem erfolgreichen Anmelden auf dem Gerät sehen wir das entsprechende Startmenü. Dieses kennen wir in ähnlicher Form von mobilen Geräten mit Windows 10 oder beispielsweise von Windows Phone. Falls der Ton zu laut ist, befinden sich auf der rechten oberen Seite der Brille zwei Knöpfe zur Regelung der Lautstärke. Befinden wir uns in einer sehr hellen oder dunklen Umgebung, so können wir auf der linken Seite der Brille die Helligkeit justieren.

Wie bediene ich die HoloLens?

Je nach Situation und Ziel gibt es verschiedene Möglichkeiten, die in Frage kommen, um die HoloLens zu steuern. Die Eingabemöglichkeiten sind die Blickrichtung, Gestik und Sprache sowie der zur HoloLens mitgelieferte Klicker. In der Mitte des Sichtfelds befindet sich ein weißer Punkt. Dieser weiße Punkt dient quasi als Mauszeiger. Entweder durch eine bestimmte Gestik (Tippbewegung mit dem Zeigefinger), ein bestimmtes Sprachkommando („Select“) oder mit dem Klicker können wir, ähnlich wie ein Mausklick, Elemente selektieren und auswählen. Je nach gestarteter App existieren auch noch andere Sprachbefehle (z.B. „Remove“ zum Schließen oder für Menüpunkte der Name des Menüeintrags) oder Gestik (z.B. ein Verschieben oder Vergrößern von Elementen).

 

 05_HoloLensMenu

Das Hauptmenü zum Starten von Apps und

Steuern des Systems (Bildquelle)

 

Was steckt in der HoloLens?

Die HoloLens ist ein Gerät, dass gerade so vor technischen Details/Ausstattungen strotzt, wenn man bedenkt, dass sie komplett kabellos ist, ein eigenständigen kleinen Rechner darstellt und auf unserem Kopf sitzt (Andere Virtual-Reality-Brillen verwenden hier ein Kabel zu einem Rechner). Besonders bemerkenswert sind die durchsichtigen holografischen Linsen, in welche die holografischen Elemente hineinprojiziert werden und sich so in das Sichtfeld des Trägers integrieren. Die weitere Sensorik dient zur Interaktion mit denen Projektionen in unserem Sichtfeld und somit der erweiterten Realität (oder auch vermischten Realität). Die notwendigen Sensoren und Komponenten für eine solche Interaktion zwischen virtueller und echter Realität sehen wie folgt aus:

Optik:
  • Durchsichtige holographische Linsen
  • Zwei HD 16:9 Lichtprojektoren
  • Automatische Distanzerkennung der Pupillen
  • Holographische Auflösung von 2.3M Lichtpunkten
Sensoren:
  • Eine IMU (inertiale Messeinheit zur Messung der Trägheitsnavigation)
  • 4 umgebungsverstehende Kameras
  • 1 Tiefenkamera
  • 1 2MP Foto-/Video-Kamera
  • 4 Mikrofone
  • 1 Umgebungslichtsensor
Menschliche Steuerung:
  • Tracking der Blickrichtung
  • Eingabe über Gestik
  • Sprachsteuerung
  • Klicker
Weiteres Erwähnenswertes:
  • Eingebaute Lautsprecher
  • 3,5mm Klinkenstecker
  • WLAN
  • Gewicht: 579g
  • Festplatte: 64GB Flash
  • Arbeitsspeicher: 2GB
  • CPU: Speziell angefertigte holografische Recheneinheit
  • […]

 

06HolographicLens

Die Holografischen Linsen (Bildquelle)

07SenorenAnsicht

Die HoloLens ohne Abdeckung und mit sichtbaren Sensoren (Bildquelle)

Was kann die HoloLens jetzt schon?

Auf der HoloLens mitinstalliert befinden sich bereits einige wenige Anwendungen. Andere Anwendungen können aus dem Windows Store heruntergeladen werden. Alle Anwendungen, die als Universal Windows Plattform App entwickelt wurden, können auf der HoloLens problemlos betrieben werden. Diese bezeichnet man auch als 2D-Anwendungen, da sie auf eine zweidimensionale Oberfläche projiziert werden. Somit können wir wie gewohnt  auf einem Desktop mit Fenstern arbeiten. Der Unterschied hier ist: Die Fenster können wir uns in die vermischte Realität pinnen. Beispielsweise kann man surfen und gleichzeitig ein Video schauen.

08_KitchenTableWith2DApps

Der Frühstückstisch wird zum interaktiven Arbeitsplatz
durch die im Raum verteilten 2-D-Anwendungen (Bildquelle)

Besonders herausragend sind hier aber die für HoloLens spezialisierten Apps, die auch eine dreidimensionale Interaktion erlauben. Es gibt hier von Microsoft schon einige Beispiel-Apps, die zeigen, welche Möglichkeiten für eine Interaktion zwischen der virtuellen und echten Welt möglich sind. Während man in diesen Anwendungen ist, scheinen wirklich beide Welten zu verschmelzen. Der Begriff „vermischte Realität“ trifft es hier voll und ganz. Beispielsweise kann man mit HoloTour verschiedene Städte auf der Welt besuchen und sich dort die besten Sehenswürdigkeiten ansehen (wie z.B. Rom). Ist mehr Action gewünscht, so kann man mit RoboRaid gegen Aliens in seinen eigenen Räumen kämpfen. Ein richtig gutes Beispiel, was jetzt schon möglich ist, zeigt die Anwendung Fragments. Hier wird nach erfolgreicher Raum-Analyse ein Krimi gestartet, in dem man selbst die Rolle eines Detektives übernimmt. Die Auftragsgeber und Tatortszenen integrieren sich meisterhaft in das Wohnzimmer bzw. den aktuell verwendeten Raum. Beispielsweise sitzt der Auftragsgeber auf dem Sofa gegenüber beim Mission-Briefing und spricht einen direkt an. Achtung: Es ist wichtig, dass der Raum entsprechend groß ist. Am Anfang der Raum-Analyse wird dies auch mitangezeigt. Microsoft bietet auch für Noch-Nicht-HoloLens-Besitzer zahlreiche Informationen zur Übersicht (inkl. Videos), was es bereits gibt und wo die Richtung hingehen soll. Reinschauen lohnt sich!

09_Example3DApp_Fragments

In Fragments sitzt man zusammen mit dem Auftragsgeber beim Mission-Briefing und erhält die Auftragsdetails (Bildquelle)

Wollen Sie mehr über HoloLens erfahren, dann sprechen Sie uns an oder melden Sie sich an beim AIT Holodeck.

Jürgen Gutsch: Setup Angular2 & TypeScript in a ASP.​NET Core project using Visual Studio

In this post I try to explain, how to setup a ASP.NET Core project with Angular2 and typescript in Visual Studio 2015.

There are two ways to setup an Angular2 Application: The most preferred way is to use angular-cli, which is pretty simple. Unfortunately the Angular CLI doesn't use the latest version . The other way is to follow the tutorial on angular.io, which sets-up a basic starting point, but this needs a lot of manually steps. There are also two ways to setup the way you want to develop your app with ASP.NET Core: One way is to separate the client app completely from the server part. It is pretty useful to decouple the server and the client, to create almost independent applications and to host it on different machines. The other way is to host the client app inside the server app. This is useful for small applications, to have all that stuff in one place and it is easy to deploy on a single server.

In this post I'm going to show you, how you can setup Angular2 app, which will be hosted inside an ASP.NET Core application using Visual Studio 2015. Using this way, the Angular-CLI is not the right choice, because it already sets up a development environment for you and all that stuff is configured a little bit different. The effort to move this to Visual Studio would be to much. I will almost follow the tutorial on http://angular.io/. But we need to change some small things to get that stuff working in Visual Studio 2015.

Configure the ASP.NET Core project

Let's start with a new ASP.NET Core project based on .NET Core. (The name doesn't matter, so "WebApplication391" is fine). We need to choose a Web API project, because the client side Angular2 App will probably communicate with that API and we don't need all the predefined MVC stuff.

A Web API project can't serve static files like JavaScripts, CSS styles, images, or even HTML files. Therefore we need to add a reference to Microsoft.AspNetCore.StaticFiles in the project.json:

"Microsoft.AspNetCore.StaticFiles": "1.0.0 ",

And in the startup.cs, we need to add the following line, just before the call of `UseMvc()

app.UseStaticFiles();

Another important thing we need to do in the startup.cs, is to support the Routing of Angular2. If the Browser calls a URL which doesn't exists on the server, it could be a Angular route. Especially if the URL doesn't contain a file extension.

This means we need to handle the 404 error, which will occur in such cases. We need to serve the index.html to the client, if there was an 404 error, on requests without extensions. To do this we just need a simple lambda based MiddleWare, just before we call UseStaticFiles():

app.Use(async (context, next) =>
{
    await next();

    if (context.Response.StatusCode == 404
        && !Path.HasExtension(context.Request.Path.Value))
    {
        context.Request.Path = "/index.html";
        await next();
    }
});

Inside the properties folder we'll find a file called launchSettings.json. This file is used to configure the behavior of visual Studio 2015, when we press F5 to run the application. Remove all strings "api/values" from this file. Because Visual Studio will always call that specific Web API, every time you press F5.

Now we prepared the ASP.NET Core application to start to follow the angular.io tutorial.:

Let's start with the NodeJS packages. Using Visual Studio we can create a new "npm Configuration file" called package.json. Just copy the stuff from the tutorial:

{
  "name": "dotnetpro-ecollector",
  "version": "1.0.0",
  "scripts": {
    "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "lite": "lite-server",
    "postinstall": "typings install && gulp restore",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "typings": "typings"
  },
  "license": "ISC",
  "dependencies": {
    "@angular/common": "2.0.0-rc.4",
    "@angular/compiler": "2.0.0-rc.4",
    "@angular/core": "2.0.0-rc.4",
    "@angular/forms": "0.2.0",
    "@angular/http": "2.0.0-rc.4",
    "@angular/platform-browser": "2.0.0-rc.4",
    "@angular/platform-browser-dynamic": "2.0.0-rc.4",
    "@angular/router": "3.0.0-beta.1",
    "@angular/router-deprecated": "2.0.0-rc.2",
    "@angular/upgrade": "2.0.0-rc.4",
    "systemjs": "0.19.27",
    "core-js": "^2.4.0",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.6",
    "zone.js": "^0.6.12",
    "angular2-in-memory-web-api": "0.0.14",
    "es6-promise": "^3.1.2",
    "es6-shim": "^0.35.0",
    "jquery": "^2.2.4",
    "bootstrap": "^3.3.6"
  },
  "devDependencies": {
    "gulp": "^3.9.1",
    "concurrently": "^2.0.0",
    "lite-server": "^2.2.0",
    "typescript": "^1.8.10",
    "typings": "^1.0.4"
  }
}

In this listing, I changed a few things:

  • I added "&& gulp restore" to the postinstall script
  • I also added Gulp to the devDependency to typings

After the file is saved, Visual Studio tryies to load all the packages. This works, but VS shows a yellow exclemation mark because of any arror. Until yet, I didn't figure out what is going wrong here. To be sure all packages are propery installed, use the console, change directory to the current project and type npm install

The post install will possibly faile because gulp is not yet configured. We need gulp to copy the dependencies to the right location inside the wwwroot folder, because static files will only be loaded from that location. This is not part of the tutorial on angular.io, but is needed to fit the client stuff into Visual Studio. Using Visual Studio we need to create a new "gulp Configuration file" with the name gulpfile.js:

var gulp = require('gulp');

gulp.task('default', function () {
    // place code for your default task here
});

gulp.task('restore', function() {
    gulp.src([
        'node_modules/@angular/**/*.js',
        'node_modules/angular2-in-memory-web-api/*.js',
        'node_modules/rxjs/**/*.js',
        'node_modules/systemjs/dist/*.js',
        'node_modules/zone.js/dist/*.js',
        'node_modules/core-js/client/*.js',
        'node_modules/reflect-metadata/reflect.js',
        'node_modules/jquery/dist/*.js',
        'node_modules/bootstrap/dist/**/*.*'
    ]).pipe(gulp.dest('./wwwroot/libs'));
});

The task restore, copies all the needed files to the Folder ./wwwroot/libs

TypeScript needs some type definitions to get the types and API definitions of the libraries, which are not written in TypeScript or not available in TypeScript. To load this, we use another tool, called "typings". This is already installed with NPM. This tool is a package manager for type definition files. We need to configure this tool with a typings.config

{
  "globalDependencies": {
    "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
    "jquery": "registry:dt/jquery#1.10.0+20160417213236"
  }
}

No we have to configure typescript itself. We can also add a new item, using Visual Studio to create a TyoeScript configuration file. I would suggest not to use the default content, but the contents from the angular.io tutorial.

{
  "compileOnSave": true,
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "node_modules"
  ]
}

The only things I did with this file, is to add the "compileOnSave" flag and to exclude the "node_modules" folder from the TypeScript build, because we don't need to build containing the TypeScript files and because we moved the needed JavaScripts to ./wwwroot/libs.

If you use Git or any other source code repository, you should ignore the files generated out of our TypeScript files. In case of Git, I simply add another .gitignore to the ./wwwroot/app folder

#remove generated files
*.js
*.map

We do this becasue the JavaScript files are only relevant to run the applicaiton and should be created automatically in the development environment or on a build server, befor deploying the app.

The first app

That is all to prepare a ASP.NET Core project in Visual Studio 2015. Let's start to create the Angular app. The first step is to create a index.html in the folder wwwroot:

<html>
<head>
    <title>dotnetpro eCollector</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="css/styles.css">
    <!-- 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="libs/shim.min.js"></script>
    <script src="libs/zone.js"></script>
    <script src="libs/Reflect.js"></script>
    <script src="libs/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
        System.import('app')
            .catch(function (err) { console.error(err); });
    </script>
</head>
<!-- 3. Display the application -->
<body>
    <my-app>Loading...</my-app>
</body>
</html>

As you can see, we load almost all JavaScript files from the libs folder. Except a systemjs.config.js. This file is needed to configure Angular2, to define which module is needed, where to find dependencies an so on. Create a new JavaScript file, call it systemjs.config.js and paste the following content into it:

(function (global) {

    // map tells the System loader where to look for things
    var map = {
        'app': 'app', 
        'rxjs': 'lib/rxjs',
        '@angular': 'lib/@angular'
    };

    // packages tells the System loader how to load when no filename and/or no extension
    var packages = {
        'app': { main: 'main.js', defaultExtension: 'js' },
        'rxjs': { defaultExtension: 'js' },
        'angular2-in-memory-web-api': { defaultExtension: 'js' },
    };

    var packageNames = [
      '@angular/common',
      '@angular/compiler',
      '@angular/core',
      '@angular/http',
      '@angular/platform-browser',
      '@angular/platform-browser-dynamic',
      '@angular/router',
      '@angular/router-deprecated',
      '@angular/upgrade'
    ];

    packageNames.forEach(function (pkgName) {
        packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
    });

    var config = {
        map: map,
        packages: packages
    }

    // filterSystemConfig - index.html's chance to modify config before we register it.
    if (global.filterSystemConfig) { global.filterSystemConfig(config); }

    System.config(config);

})(this);

This file also defines a main entry point which is a main.js. This file is the transpiled TypeScript file main.ts we need to create in the next step. The main.ts bootstraps our Angular2 app:

import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

bootstrap(AppComponent);

We also need to create our first Angular2 component. Create a TypeScript file with the name app.component.ts inside the app folder:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: '<h1>My first Angular App in Visual Studio</h1>'
})
export class AppComponent { }

If all works fine, Visual Studio should have created a JavaScript file for each TypeScript file. Also the build should run. Pressing F5 should start the Application and a Browser should open.

A short moment the Loading... is visible in the browser. After the app is initialized and all the Angular2 magic happened, you'll see the contents of the template defined in the app.component.ts.

Conclusion

I propose to use VisualStudio just for small single page applications, because it gets slower the more dynamic files need to be handled. ASP.NET Core is pretty cool to handle dynamically generated files, but Visual Studio still is not. VS tries to track and manage all the files inside the project, which slows down a lot. One solution is to disable source control in Visual Studio and use an external tool to manage the sources.

Another - even better - solution is not to use Visual Studio for front-end development. In a new project, I propose to separate front-end and back-end development and to use Visual Studio Code for the front-end development or even both. You need to learn a few things about NPM, Gulp and you need to use a console in this case, but web development will be a lot faster and a lot more lightweight with this approach. In one of the next posts, I'll show how I currently work with Angular2.

Martin Hey: GZip für WebAPI 2 aktivieren

Mit GZip-Komprimierung kann man einiges an Netzwerklast minimieren - insbesondere, wenn größere Datenmengen übertragen werden sollen, in denen häufig ähnliche Worte vorkommen. Auch wenn das meist genutzte JSON-Format nicht ganz so geschwätzig ist wie beispielsweise SOAP, so kommen doch auch hier beispielsweise die Eigenschaftsnamen in jedem Objekt wieder vor. Wenn man dann eine Liste von Objekten überträgt, so ergibt sich hier ein Einsparungspotenzial.


Im  Gegensatz zu Content-Negotiation, die die Web-API selbst übernimmt, gibt es offenbar keinen Automatismus für die Gzip-Komprimierung. Eine Suche ergab, verschiedene Lösungsansätze - z.B. den von Ben Foster oder den von Radenko Zec. Letzterer ist Vorlage für meine jetzige Lösung geworden.

Schritt 1 - Erzeugen eines ActionFilters, der die Response ändert
public class CompressionAttribute : ActionFilterAttribute
{
    public override async Task OnActionExecutedAsync(HttpActionExecutedContext context, CancellationToken cancellationToken)
    {
        var acceptEncoding = context.Request.Headers.AcceptEncoding;
        var acceptsGzip = acceptEncoding.Contains(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));

        if (!acceptsGzip)
        {
            return;
        }

        var content = context.Response.Content;
        if (content == null)
        {
            return;
        }

        var headers = context.Response.Content.Headers;
        var bytes = await content.ReadAsByteArrayAsync();

        var zlibbedContent = (await Compress(bytes)) ?? new byte[0];
        context.Response.Content = new ByteArrayContent(zlibbedContent);

        foreach (var header in headers)
        {
            if (header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }
            context.Response.Content.Headers.Add(header.Key, header.Value);
        }
        context.Response.Content.Headers.Add("Content-Encoding", "gzip");
    }

    private static async Task Compress(byte[] value)
    {
        if (value == null)
        {
            return null;
        }

        using (var output = new MemoryStream())
        {
            using (var gzipStream = new GZipStream(output, CompressionMode.Compress, CompressionLevel.BestSpeed))
            {
                gzipStream.FlushMode = FlushType.Finish;
                await gzipStream.WriteAsync(value, 0, value.Length);
                   
            }
            return output.ToArray();
        }
    }
}
Um die Komprimierung selbst kümmert sich Ionic.Zlib

Die Implementierung ist an sich recht einfach. Zunächst wird geprüft, ob der Client Gzip-Encoding akzeptiert. Ist das nicht der Fall, dann verändert das Attribut die Ausgabe nicht. Anderenfalls wird die Ausgabe komprimiert und die Response neu aufgebaut. Durch die Neuzuweisung der Response werden auch alle bisherigen Content-Type-Header verworfen, weswegen diese im Nachgang wieder gesetzt werden müssen.

Im Anschluss wird dann dem Client noch mitgeteilt, dass es sich um komprimierten Inhalt handelt.

Schritt 2 - ActionFilter anwenden
Der ActionFilter kann nun verwendet werden, indem eine Web-API-Methode damit annotiert wird oder man aktiviert ihn global für alle Anfragen.
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ...

        config.Filters.Add(new CompressionAttribute());

        ...
    }
}
Das passiert in der WebApiConfig.

Jürgen Gutsch: Add HTTP headers to static files in ASP.​NET Core

Usually, static files like JavaScript, CSS, images and so on, are cached on the client after the first request. But sometimes, you need to disable the cache or to add a special cache handling.

To provide static files in a ASP.NET Core application, you use the StaticFileMiddleware:

app.UseStaticFiles();

This extension method has two overloads. One of them needs a StaticFileOptions instance, which is our friend in this case. This options has a property called OnPrepareResponse of type Action<StaticFileResponseContext>. Inside this Action, you have access to the HttpContext and many more. Let's see how it looks like to set the cache life time to 12 hours:

app.UseStaticFiles(new StaticFileOptions()
{
    OnPrepareResponse = context =>
    {
        context.Context.Response.Headers["Cache-Control"] = 
                "private, max-age=43200";

        context.Context.Response.Headers["Expires"] = 
                DateTime.UtcNow.AddHours(12).ToString("R");
    }
});

With the StaticFileResponseContext, you also have access to the file of the currently handled file. With this info, it is possible to manipulate the HTTP headers just for a specific file or file type.

This approach ensures, that the client doesn't use pretty much outdated files, but use cached versions while working with it. We use this in a ASP.NET Core single page application, which uses many JavaScript, and HTML template files. In combination with continuous deployment, we need to ensure the Application uses the latest files.

Norbert Eder: XWiki: List of child-documents

I wrote about XWiki some time ago. It is a pretty cool wiki with a lot of features and a modern look. As I write articles about XWiki, my decision pro or against XWiki/Confluence shouldn’t be hard to guess. Exactly, I gave XWiki a chance.

Some of my wiki pages should just list and link all child pages automatically, so I haven’t to add them manually. Usually this is done by a tree in the sidebar which I disabled in my installation (for several reason, but they doesn’t matter so far). There is macro for this: Document Tree Macro.

In order to create a dynamic list/tree use this piece of “code”:

{{velocity}}
  {{documentTree root="document:$doc.fullName"/}}
{{/velocity}}

This should do the trick.

Please see the documentation for a description of all possible parameters.

The post XWiki: List of child-documents appeared first on Norbert Eder.

friends header

bloggers headline

links header

 
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.