Golo Roden: Eine Referenz für Domain-Driven Design

Der Einstieg in Domain-Driven Design fällt häufig schwer, weil die Terminologie von DDD an sich schwer verständlich und zunächst verwirrend wirkt. Abhilfe schaffen ein anschaulicher Einstieg und eine gute Referenz.

Stefan Lieser: Unit Tests are your Friends

Angeregt durch die Diskussion mit Teilnehmern eines Clean Code Developer Workshops habe ich mich wieder einmal mit der Frage befasst, welche Strategien beim automatisierten Testen ich anwende und empfehle. Die Diskussion drehte sich vor allem um die Frage, ob private Methoden durch Unit Tests getestet werden sollen und wie dies dann technisch am besten realisiert ... Read more

Der Beitrag Unit Tests are your Friends erschien zuerst auf Refactoring Legacy Code.

Jürgen Gutsch: Unit testing data access in ASP.​NET Core

I really like to be in contact with the dear readers of my blog. I get a lot of positive feedback about my posts via twitter or within the comments. That's awesome and that really pushed me forward to write more posts like this. Some folks also create PRs for my blog posts on GitHub to fix typos and other errors of my posts. You also can do this, by clicking the link to the related markdown file on GitHub at the end of every post.

Many thanks for this kind of feedback :-)

The reader Mohammad Reza recently asked me via twitter to write about unit testing an controller that connects to a database and to fake the data for the unit tests.

@sharpcms Hello jurgen, thank you for your explanation of unit test : Unit Testing an ASP.NET Core Application. it's grate. can you please explain how to use real data from entity framwork and fake data for test in a controller?

@Mohammad: First of all: I'm glad you like this post and I would be proud to write about that. Here it is:

Setup the solution using the .NET CLI

First of all, let's create the demo solution using the .NET CLI

mkdir UnitTestingAspNetCoreWithData & cd UnitTestingAspNetCoreWithData
dotnet new mvc -n WebToTest -o WebToTest
dotnet new xunit -n WebToTest.Tests -o WebToTest.Tests
dotnet new sln -n UnitTestingAspNetCoreWithData

dotnet sln add WebToTest
dotnet sln add WebToTest.Tests

This lines are creating a solution directory adding a web to test and a XUnit test project. Also a solution file gets added and the two projects will be added to the solution file.

dotnet add WebToTest.Tests reference WebToTest

This command won't work in the current version of the .NET Core, because the XUnit project still targets netcoreapp2.2. You cannot reference a higher target version. It should be equal or lower than the target version of the referencing project. You should change the the target to netcoreapp3.0 in the csproj of the test project before executing this command:

<TargetFramework>netcoreapp3.0</TargetFramework>

Now we need to add some NuGet references:

dotnet add WebToTest package GenFu
dotnet add WebToTest.Tests package GenFu
dotnet add WebToTest.Tests package moq

At first we add GenFu, which is a dummy data generator. We need it in the web project to seed some dummy data initially to the database and we need it in the test project to generate test data. We also need Moq to create fake objects, e.g. fake data access in the test project.

Because the web project is an empty web project it also doesn't contain any data access libraries. We need to add Enitity Framework Core to the project.

dotnet add WebToTest package Microsoft.EntityFrameworkCore.Sqlite -v 3.0.0-preview.18572.1
dotnet add WebToTest package Microsoft.EntityFrameworkCore.Tools -v 3.0.0-preview.18572.1
dotnet add WebToTest package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore -v 3.0.0-preview.18572.1

I'm currently using the preview version of .NET Core 3.0. The version number will change later on.

Now we can start Visual Studio Code

code .

In the same console window we can call the following command to execute the tests:

dotnet test WebToTest.Tests

Creating the controller to test

The Controller we want to test is an API controller that only includes two GET actions. It is only about the concepts. Testing additional actions, POST and PUT actions is almost the same. This is the complete controller to test.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using WebToTest.Data.Entities;
using WebToTest.Services;

namespace WebToTest.Controllers
{
    [Route("api/[controller]")]
    [ApiController()]
    public class PersonController : Controller
    {
        private readonly IPersonService _personService;

        public PersonController(IPersonService personService)
        {
            _personService = personService;
        }
        // GET: api/Person
        [HttpGet]
        public IEnumerable<Person> GetPersons()
        {
            return _personService.AllPersons();
        }

        // GET: api/Person/5
        [HttpGet("{id}")]
        public ActionResult<Person> GetPerson(int id)
        {
            var todoItem = _personService.FindPerson(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            return todoItem;
        }
    }
}

As you can see, we don't use entity framework directly in the controller. I would propose to encapsulate the data access in service classes, which prepare the data as you need it.

Some developers prefer to encapsulate the actual data access in an additional repository layer. From my perspective this is not needed, if you use an OR mapper like Entity Framework. One reason is that EF already is the additional layer that encapsulates the actual data access. And the repository layer is also an additional layer to test and to maintain.

So the service layer contains all the EF stuff and is used here. This also makes testing much easier because we don't need to mock the EF DbContext. The Service gets passed in via dependency injection.

Let's have a quick look into the Startup.cs where we need to configure the services:

public void ConfigureServices(IServiceCollection services)
{
    // [...]

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

    services.AddTransient<IPersonService, PersonService>();
    
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

What I added to the ConfigureServices method is one line to register and configure the DbContext and one line to register the PersonService used in the controller. Both types are not created yet. Before we create them we also need to add a few lines to the config file. Open the appsettings.json and add the connection string to the SQLite database:

{
  "ConnectionStrings": {
    "DefaultConnection": "DataSource=app.db"
  },
  // [...]
}

That's all about the configuration. Let's go back to the implementation. The next step is the DbContext. To keep the demo simple, I just use one Person entity here:

using GenFu;
using Microsoft.EntityFrameworkCore;
using WebToTest.Data.Entities;

namespace WebToTest.Data
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {}
        
        public ApplicationDbContext() { }

		protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // seeding
            var i = 1;
            var personsToSeed = A.ListOf<Person>(26);
            personsToSeed.ForEach(x => x.Id = i++);
            modelBuilder.Entity<Person>().HasData(personsToSeed);
        }

        public virtual DbSet<Person> Persons { get; set; }
    }
}

We only have one DbSet of Person here. In the OneModelCreating method we use the new seeding method HasData() to ensure we have some data in the database. Usually you would use real data to seed to the database. In this case I use GenFu do generate a list of 26 persons. Afterwards I need to ensure the IDs are unique, because by default GenFu generates random numbers for the ids which may result in a duplicate key exception.

The person entity is simple as well:

using System;

namespace WebToTest.Data.Entities
{
    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Address { get; set; }
        public string Telephone { get; set; }
        public string Email { get; set; }
    }
}

Now let's add the PersonService which uses the ApplicationDbContext to fetch the data. Even the DbContext gets injected into the constructor via dependency injection:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using WebToTest.Data;
using WebToTest.Data.Entities;

namespace WebToTest.Services
{
    public class PersonService : IPersonService
    {
        private readonly ApplicationDbContext _dbContext;
        public PersonService(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public IEnumerable<Person> AllPersons()
        {
            return _dbContext.Persons
            	.OrderBy(x => x.DateOfBirth)
            	.ToList();
        }

        public Person FindPerson(int id)
        {
            return _dbContext.Persons
            	.FirstOrDefault(x => x.Id == id);
        }
    }

    public interface IPersonService
    {
        IEnumerable<Person> AllPersons();
        Person FindPerson(int id);
    }
}

We need the interface to register the service using a contract and to create a mock service later on in the test project.

If this is done, don't forget to create an initial migration to create the database:

dotnet ef migrations add Initial -p WebToTest -o Data\Migrations\

This puts the migration into the Data folder in our web project. No we are able to create and seed the database:

dotnet ef database update -p WebToTest

In the console you will now see how the database gets created and seeded:

Now the web project is complete and should run. You can try it by calling the following command and calling the URL https://localhost:5001/api/person in the browser:

dotnet run -p WebToTest

You now should see the 26 persons as JSON in the browser:

Testing the controller

In the test project I renamed the initially scaffolded class to PersonControllerTests. After that I created a small method that creates the test data we'll return to the controller. This is exactly the same code we used to seed the database:

private IEnumerable<Person> GetFakeData()
{
    var i = 1;
    var persons = A.ListOf<Person>(26);
    persons.ForEach(x => x.Id = i++);
    return persons.Select(_ => _);
}

We now can create out first test to test the controllers GetPersons() method:

[Fact]
public void GetPersonsTest()
{
    // arrange
    var service = new Mock<IPersonService>();

    var persons = GetFakeData();
    service.Setup(x => x.AllPersons()).Returns(persons);

    var controller = new PersonController(service.Object);

    // Act
    var results = controller.GetPersons();

    var count = results.Count();

    // Assert
    Assert.Equal(count, 26);
}

In the first line we use Moq to create a mock/fake object of our PersonService. This is why we need the interface of the service class. Moq creates proxy objects out of interfaces or abstract classes. Using Moq we are now able to setup the mock object, by telling Moq we want to return this specific list of persons every time we call the AllPersons() method.

If the setup is done we are able to inject the proxy object of the IPersonService into the controller. Our controller now works with a fake service instead using the original one. Inside the unit test we don't need a connection to the database now. That makes the test faster and more independent from any infrastructure outside the code to test

In the act section we call the GetPersons() method and will check the results afterwards in the assert section.

How does it look like with the GetPerson() method that returns one single item?

The second action to test returns an ActionResult of Person, so we only need to get the result a little bit different:

[Fact]
public void GetPerson()
{
    // arrange
    var service = new Mock<IPersonService>();

    var persons = GetFakeData();
   	var firstPerson = persons.First();
    service.Setup(x => x.FindPerson(1)).Returns(firstPerson);

    var controller = new PersonController(service.Object);

    // act
    var result = controller.GetPerson(1);
    var person = result.Value;

    // assert
    Assert.Equal(1, person.Id);
}

Also the setup differs, because we setup another method that returns a single Person instead of a IEnumerable of Person.

To execute the tests run the next command in the console:

dotnet test WebToTest.Tests

This should result in the following output if all is done right:

Testing the service layer

How does it look like to test the service layer? In that case we need to mock the DbContext to feed the service with fake data.

In the test project I created a new test class called PersonServiceTests and a test method that tests the AllPersons() method of the PersonService:

[Fact]
public void AllPersonsTest()
{
    // arrange
    var context = CreateDbContext();

    var service = new PersonService(context.Object);

    // act
    var results = service.AllPersons();

    var count = results.Count();

    // assert
    Assert.Equal(26, count);
}

This looks pretty simple at the first glance, but the magic is inside the method CreateDbContext, which created the mock object of the DbContext. I don't return the actual object, in case I need to extend the mock object in the current test method. Let's see how the DbContext is created:

private Mock<ApplicationDbContext> CreateDbContext()
{
    var persons = GetFakeData().AsQueryable();

    var dbSet = new Mock<DbSet<Person>>();
    dbSet.As<IQueryable<Person>>().Setup(m => m.Provider).Returns(persons.Provider);
    dbSet.As<IQueryable<Person>>().Setup(m => m.Expression).Returns(persons.Expression);
    dbSet.As<IQueryable<Person>>().Setup(m => m.ElementType).Returns(persons.ElementType);
    dbSet.As<IQueryable<Person>>().Setup(m => m.GetEnumerator()).Returns(persons.GetEnumerator());

    var context = new Mock<ApplicationDbContext>();
    context.Setup(c => c.Persons).Returns(dbSet.Object);
    return context;
}

The DbSet cannot be easily created, it is a bit special. This is why I need to mock the DbSet and to setup the Provider, the Expression the ElementType and the Enumerator using the values from the persons list. If this is done, I can create the ApplicationDbContext mock and setup the DbSet of Person on that mock. For every DbSet in your DbContext you need to add this four special setups on the mock DbSet. This seems to be a lot of overhead, but it is worth the trouble to test the service in an isolated way.

Sure you could use a in memory database with a real DbContext, but in this case the service isn't really isolated and and we anyway have a kind of a unit test.

The second test of the PersonService is pretty similar to the first one:

[Fact]
public void FindPersonTest()
{
    // arrange
    var context = CreateDbContext();

    var service = new PersonService(context.Object);

    // act
    var person = service.FindPerson(1);

    // assert
    Assert.Equal(1, person.Id);
}

Let's run the tests and see if it's all working as expected:

dotnet test WebToTest.Tests

Also this four tests passed.

Summary

In this tutorial the setup took the biggest part, just to get a running API controller that we can use to test.

  • We created the solution in the console using the .NET CLI.
  • We added a service layer to encapsulate the data access.
  • We added a EF DbContext to use in the service layer.
  • We registered the services and the DbContext in the DI.
  • We used the service in the controller to create two actions which return the data.
  • We started the application to be sure all is running fine.
  • We created one test on an action that doesn't return an ActionResult
  • We created another test on an action that returns an ActionResult
  • We ran the tests successfully in the console using the .NET CLI

Not using the DbContext in the Controller directly makes it a lot easier to test the controller by passing in a mock service. Why? Because it is easier to fake the service instead of the DbContext. It also keeps the controller small which makes maintenance a lot easier later on.

Faking the DbContext is a bit more effort, but also possible as you saw in the last section.

Please find the complete code sample her on GitHub: https://github.com/JuergenGutsch/unit-testing-aspnetcore3

Conclusion

@Mohammad I hope this post will help you and answer your questions :-)

Using ASP.NET Core there is no reason not to unit test the most important and critical parts of your application. If needed you are able to unit test almost all in your ASP.NET Core application.

Unit test is no magic but it is also not the general solution to ensure the quality of your app. To ensure that all tested units are working together you definitely need to have some some integration tests.

I'll do another post about integration tests using ASP.NET Core 3.0 soon.

Albert Weinert: Mein Twitch.TV Kanal ist nun Live

Live und in Farbe

Letztes Jahr habe ich nach jahrelangen zögern nun endlich auch das Live Coding auf Twitch.TV angefangen. Dies habe ich schon ewig vor. Tom Wendel hatte schon früh diesen Trend gesehen und mit Octo Awesome und anderen Projekten viele Stunden Live gestreamt. Dies wollte ich auch machen, kann doch nicht so schwer sein.

Aber, will mich überhaupt jemand sehen oder auch noch hören haben mich lange in meiner Komfortzone sitzen lassen. Jeodch auch wegen Fritz & Friends und CodeRushed dachte ich mir immer mehr, mache es einfach.

Dann habe ich den Schritt gemacht. Ich finde es entwickelt sich ganz prächtig. Natürlich übe ich noch rund um das Streaming, auch wie ich vor der Kamera gebe oder was ich so sage.

Falls Ihr Interesse habt schaut einfach mal rein.

Wo findet Ihr es?

Was gab es bis jetzt?

Es wird z.b. Live eine Konferenz Site als PWA entwickelt. Mit der sollen sich Teilnehmer einer Konferenz Ihren Session Plan für den Tag erstellen können. Die OpenSource Lizenz unter der es stehen wird ist noch definiert. Aber sie wird frei verwendbar sein.

Auch gab es schon zwei Erklärbär Videos.

Eins wo ich AdHoc etwas zum ASP.NET Core Options System erzähle.

Und eins wo mir Mike Build was über GraphQL erklärt.

Auch wurde ein kleines Tool, der Browser Deflector entwickelt und via Azure DevOps auf GitHub deployed. Mit dem man für bestimmte Urls bestimmte Browser starten kann.

Was ist geplant?

Geplant ist so einiges, ich möchte mehr Live Streams machen wie der mit Mike Bild, wo Experten zu einem Thema etwas im Dialog erzählen und gleichzeitig dazu etwas zeigen

Es wird weiter Live gecodet an verschiedenen Projekten

  • Fertigstellung der Konferenz App
  • Entwicklungs eines Familien Kalendars ala Dakboard
  • Einen auf Arduino und Light Stripes basierender Durchhaltezähler (auf Wunsch einer einzelnen Dame)
  • was anderes

Diese Angaben sind ohne gewähr ;)

Auch ist geplant ein einen ASP.NET Core Authentication & Authorization Deep Dive zu machen, aber erst wenn ich 100 Follower auf meinem Twitch Kanal habe.

Wenn dieser Euch interessiert könnte Ihr hier schon vorab eure Fragen & Herausforderungen einkippen. So dass ich diese berücksichtigen kann.

Sonst noch was?

Twitch is ein Amazon Dienst, wenn Ihr Amazon Prime habt könnt Ihr eurer Twitch Konto damit verknüpfung und schaut Werbefrei.

Während des Streams gibt es einen Live Chat, darin könnt Ihr mit mir und anderen interagieren. Um dort rein schreiben zu können ist ein Twitch Konto erforderlich.

Ich würde mich freuen wenn sich noch mehr Leute zu den Live Streams einfinden, und natürlich mir dort folgen. Damit werdet wenn Ihr wollt von Twitch über den Start des Streams informiert. Regelmäßige Zeiten gibt es noch nicht, aber besondere Events wie das mit Mike oder der Deep Dive werden natürlich entsprechend terminiert.

Holger Schwichtenberg: Alle Grafikdateien aus einem Word-Dokument exportieren

Mit einem PowerShell-Skript, das man per Kontextmenü aufruft, bekommt man ganz schnell einen Ordner mit allen Grafiken, die sich in einem Word-DOCX befinden.

Norbert Eder: Ziele 2019

Wie jedes Jahr, nehme ich mir auch für das kommende Jahr wieder einige Ziele vor. Einige davon möchte ich hiermit öffentlich zugänglich machen, einige werden alleine nur für mich (bzw. für einen nicht-öffentlichen Kontext) existieren.

Softwareentwicklung

Das kommende Jahr wird sich voraussichtlich um die Themen .NET Core, Golang, Container, Serverless und IoT drehen. Es stehen einige große Projekte ins Haus. Hierzu wird viel architekturelle aber auch sicherheitsrelevante Arbeit zu leisten sein. Die Ziele liegen hierbei weniger bei den einzelnen Technologien, sondern vielmehr in der Größenordnung und damit einhergehenden architekturellen und performancetechnischen Herausforderungen.

Bewertbares Ziel: Mehr Fachbeiträge hier im Blog als 2018.

Fotografie

Ich war 2018 sehr viel unterwegs und es sind tolle Fotos entstanden. Allerdings fand ich nicht die Muse, diese auch online zu präsentieren. 2019 möchte ich mehr in meine Portfolios investieren und sie aufmotzen. Auch soll es wieder meine Reiseberichte geben.

Nachdem ich bereits im „Portrait-Modus“ bin, werde ich anschließen und mehr in dieses Thema investieren.

Neben der Fotografie möchte ich mich auch mit dem Thema Video mehr beschäftigen. In diesen Bereich fallen viele andere Themen wie Sound, Bearbeitungssoftware usw. Meinen Weg möchte ich dokumentieren und somit einen Einblick in dieses Thema liefern.

Auch zum Thema Bildbearbeitung wird es einige Tutorials geben. Dabei werde ich mich mit der Software Luminar auseinandersetzen.

Blog

Nachdem voriges Jahr einige Aufgaben liegen geblieben sind, möchte ich diese nun 2019 erledigen. D.h. weniger Fotografie auf meiner Website, sondern wieder mehr Softwareentwicklung, IT und Technik. Die Fotografie wird auf meine Website https://norberteder.photography „verdrängt“. Der Umbau hat bereits begonnen.

Im Rückblick 2018 habe ich das Ende meines Projektes #fotomontag angekündigt – zumindest auf dieser Website. Es geht weiter, etwas verändert, aber doch. Ebenfalls auf https://norberteder.photography.

Lesen

Nachdem ich voriges Jahr relativ schnell meine ursprüngliches Ziel von 15 Büchern auf 50 angehoben habe, möchte ich dieses Jahr mit einem Ziel von 25 Büchern loslegen. Den Fortschritt könnt ihr auch dieses Jahr wieder auf https://goodreads.com verfolgen.

Zum Schluss möchte ich euch ein wunderbares Jahr 2019 wünschen. Viel Gesundheit, Glück, aber auch Erfolg.

Der Beitrag Ziele 2019 erschien zuerst auf Norbert Eder.

Norbert Eder: Rückblick 2018

Rückblick 2018

Die Jahren fliegen dahin. Schon wieder ist eines rum. Wie jedes Jahr, möchte ich einen Blick zurück werfen und über die vergangenen 365 Tage nachdenken. Dabei möchte natürlich meine Ziele für 2018 nicht außer Acht lassen und in die Bewertung einfließen lassen.

Softwareentwicklung

Wie ich es mir vorgenommen habe, beschäftigte ich mich 2018 sehr viel mit .NET Core, Angular und dem Thema Internet of Things (IoT). Zusätzlich habe ich wieder über den Tellerrand geguckt und bei Golang reingeschnuppert. Gerade Golang entwickelt sich bei mir persönlich zu einer beliebten Programmiersprache.

Sehr viel Energie ging 2018 in das Thema Docker, Microservices und Orchestrierung.

Fotografie

Auch 2018 habe ich es geschafft, jeden Montag ein Foto für mein Projekt #fotomontag zu veröffentlichen. Nach 209 veröffentlichten Fotos in 4 Jahren, geht diese Ära allerdings zu Ende.

Zusätzlich hat sich gerade auf dem Gebiet der Fotografie sehr viel bei mir getan:

  • Zahlreiche wissenswerte Beiträge auf https://norberteder.photography, auch das Portfolio wurde überarbeitet und erweitert
  • Ich habe ja eine Liste meines Foto-Equipments auf meinem Blog. Dieser musste wieder adaptiert werden :)
  • Dieses Jahr habe ich es endlich geschafft und zahlreiche Portrait-/Model-Shootings gemacht. Dabei konnte ich richtig tolle Erfahrungen sammeln und mich weiterentwickeln. Vielen Dank an dieser Stelle an alle, mit denen ich zusammenwirken konnte.
  • Schlussendlich gab es eine Menge Fotoreisen: Piran/Portoroz, Dubrovnik, Kotor, Nürnberg, Ostsee, Dresden, Budapest und Prag.

Wie du siehst, hat sich also wirklich viel getan – mehr als ich mir erhofft bzw. geplant hatte.

Blog

Für das Blog hatte ich mir mehr vorgenommen. Zwar habe ich einige Artikel ausgemistet und aktualisiert, doch wollte ich wieder viel mehr über Softwareentwicklung bloggen. Durch die Arbeit einerseits und das doch aufwändige Hobby der Fotografie andererseits, blieb einfach zu wenig Zeit, die ich dann doch anderweitig nutzte.

Seit diesem Jahr steht die Kommentarfunktion nicht mehr zur Verfügung. Das ist nicht der DSGVO geschuldet, sondern vielmehr der Qualität. Feedback erhalte ich nun über andere Kanäle (E-Mail hauptsächlich) und das qualitativ hochwertiger, da der Aufwand für das Feedback einfach höher ist.

Ein herzliches Dankeschön an meine treuen Leser, die mir die Stange halten und auch immer wieder Feedback geben.

Bücher

Seit diesem Jahr verwalte ich meine Bücher über goodreads. Meinen Account hatte ich zwar schon lange, aber das Potential blieb mir lange Zeit verborgen. Nun, seit heuer nutze ich diese Plattform.

Für 2018 hatte ich mir vorgenommen, 15 Bücher zu lesen. Tatsächlich wurden es 50 (nicht ausschließlich Fach- bzw. Sachbücher).

Top 5 Beiträge

Fazit

Insgesamt hielt das Jahr 2018 zahlreiche Herausforderungen bereit. Das war nicht immer leicht, allerdings gab es wieder viel zu lernen – und das ist wichtig und gut.

Der Beitrag Rückblick 2018 erschien zuerst auf Norbert Eder.

Alexander Schmidt: Azure Active Directory B2C – Teil 2

Dieser Teil widmet sich vor allem den Themen Identity Provider, Policies und User attributes.

Norbert Eder: Visual Studio 2017: Service Fabric Templates werden nicht angezeigt

Du hast das Azure Service Fabric SDK installiert, allerdings findest du im Visual Studio 2017 das Projekt-Template nicht und kannst somit kein neues Projekt anlegen? In diesem Fall sind eventuell die Service Fabric Tools des Azure Entwicklungsmoduls nicht installiert:

Service Fabric Tools für Visual Studio 2017 installieren

Service Fabric Tools für Visual Studio 2017 installieren

Es ist im Visual Studio Installer die Azure Entwicklung zu aktivieren, ebenso die Service Fabric-Tools.

Nach der Installation und des erneuten Startes von Visual Studio 2017 sind die Templates vorhanden.

Service Fabric Application Template | Visual Studio 2017

Service Fabric Application Template | Visual Studio 2017

Viel Spaß bei der Entwicklung.

Der Beitrag Visual Studio 2017: Service Fabric Templates werden nicht angezeigt erschien zuerst auf Norbert Eder.

Holger Schwichtenberg: Ein Git-Repository in Azure DevOps per Kommandozeile anlegen

Microsoft stellt zur automatisierten Verwaltung von Azure DevOps ein Kommandozeilenwerkzeug mit Namen "VSTS CLI" bereit.

Christina Hirth : Base your decisions on heuristics and not on gut feeling

As a developer we tackle very often problems which can be solved in various ways. It is ok not to know how to solve a problem. The real question is: how to decide which way to go 😯

In this situations often I rather have a feeling as a concrete logical reason for my decisions. This gut feelings are in most cases correct – but this fact doesn’t help me if I want to discuss it with others. It is not enough to KNOW something. If you are not a nerd from the 80’s (working alone in a den) it is crucial to be able to formulate and explain and share your thoughts leading to those decisions.

Finally I found a solution for this problem as I saw the session of Mathias Verraes about Design Heuristics held by the KanDDDinsky.

The biggest take away seems to be a no-brainer but it makes a huge difference: formulate and visualize your heuristics so that you can talk about concrete ideas instead of having to memorize everything what was said – or what you think it was said.

Using this methodology …

  • … unfounded opinions like “I think this is good and this is bad” won’t be discussed. The question is, why is something good or bad.
  • … loop backs to the same subjects are avoided (to something already discussed)
  • … the participants can see all criteria at once
  • … the participants can weight the heuristics and so to find the probably best solution

What is necessary for this method? Actually nothing but a whiteboard and/or some stickies. And maybe to take some time beforehand to list your design heuristics. These are mine (for now):

  • Is this a solution for my problem?
  • Do I have to build it or can I buy it?
  • Can it be rolled out without breaking neither my features as everything else out of my control?
  • Breaks any architecture rules, any clean code rules? Do I have a valid reason to break these rules?
  • Can lead to security leaks?
  • Is it over engineered?
  • Is it much to simple, does it feel like a short cut?
  • If it is a short cut, can be corrected in the near future without having to throw away everything? = Is my short cut implemented driving my code in the right direction, but in more shallow way?
  • Does this solution introduce a new stack = a new unknown complexity?
  • Is it fast enough (for now and the near future)?
  • … to be continued 🙂

The video for the talk can be found here. It was a workshop disguised as a talk (thanks again Mathias!!), we could have have continued for another hour if it weren’t for the cold beer waiting 🙂

David Tielke: DDC 2018 - Inhalte meines Workshops und der DevSessions

Alle Jahre wieder kommt nicht nur das Christuskind, sondern findet auch die .NET Developer Conference im Pullman-Hotel in Köln statt. In diesem Jahr veranstaltete Developer Media wieder vom 26. bis 28. November die größte .NET-Konferenz im deutschsprachigen Raum. Auch ich durfte zum neunten Mal als Sprecher teilnehmen und dabei einen Workshop und zwei DevSessions beitragen. 

Ich möchte an dieser Stelle noch einmal allen Teilnehmern meiner Veranstaltungen für drei grandiose Tage danken, es hat wie immer wahnsinnig viel Spaß gemacht. Ebenfalls ein großer Dank geht an den Veranstalter, der erneut eine tolle Konferenz geboten hat.

Wie in meinen Vorträgen angekündigt, gibt es hier nun alle Inhalte meiner Sessions zum download. Das Passwort dazu wurde in den Sessions bekanntgegeben.

Links
Downloadbereich auf OneDrive

Code-Inside Blog: How to use TensorFlow with AMD GPU's

How to use TensorFlow with AMD GPU’s

Most machine learning frameworks that run with a GPU support Nvidia GPUs, but if you own a AMD GPU you are out of luck.

Recently AMD has made some progress with their ROCm platform for GPU computing and does now provide a TensorFlow build for their gpus.

Since I work with tensorflow and own a AMD GPU it was time to give it a try. I stumpled upon these instructions for TensorFlow 1.8 but since they are outdated, I decided to write down what I did.

1. Set up Linux

It looks like there is currently no ROCm support for Windows. And no, WSL aka Bash for Windows does not work. But there are packages for CentOS/RHEL 7 and Ubuntu. I used Ubuntu 18.04.

2. Install ROCm

Just follow the ROCm install instructions.

3. Install TensorFlow

AMD provides a special build of TensorFlow. Currently they support TensorFlow 1.12.0. You can build it yourself, but the most convenient way to use it, is to install the package from PyPI:

sudo apt install python3-pip 
pip3 install --user tensorflow-rocm

4. Train a Model

To test your setup you can run the image recognition task from the Tensorflow tutorials.

git clone https://github.com/tensorflow/models.git
cd models/tutorials/image/imagenet
python3 classify_image.py

and the result should look like this:

giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.89103)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00810)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00258)
custard apple (score = 0.00149)
earthstar (score = 0.00141)

Extra: Monitor your GPU

If you like to check that your model fully utilize your GPU, you can use the radeontop tool:

Install it with

sudo apt-get install radeontop

and run it

sudo radeontop

This will dump the statistics to the command line.

sudo radeontop -d -

Holger Schwichtenberg: Microsoft Connect(); Online-Konferenz am 4. Dezember 2018

Die Vorträge sind am heute Abend ab 17:30 Uhr deutscher Zeit kostenlos zu sehen. Was ist zu erwarten?

Code-Inside Blog: Make your WCF Service async

Oh my: WCF???

This might be the elephant in the room: I wouldn’t use WCF for new stuff anymore, but to keep some “legacy” stuff working it might be a good idea to modernize those services as well.

WCF Service/Client compatibility

WCF services had always close relationships with their clients and so it is no suprise, that most guides show how to implement async operations on the server and client side.

In our product we needed to ensure backwards compatibility with older clients and to my suprise: Making the operations async don’t break the WCF contract!.

So - a short example:

Sync Sample

The sample code is more or less the default implementation for WCF services when you use Visual Studio:

[ServiceContract]
public interface IService1
{
    [OperationContract]
    string GetData(int value);

    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);

    // TODO: Add your service operations here
}

[DataContract]
public class CompositeType
{
    bool boolValue = true;
    string stringValue = "Hello ";

    [DataMember]
    public bool BoolValue
    {
        get { return boolValue; }
        set { boolValue = value; }
    }

    [DataMember]
    public string StringValue
    {
        get { return stringValue; }
        set { stringValue = value; }
    }
}

public class Service1 : IService1
{
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }

    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {
        if (composite == null)
        {
            throw new ArgumentNullException("composite");
        }
        if (composite.BoolValue)
        {
            composite.StringValue += "Suffix";
        }
        return composite;
    }
}

The code is pretty straight forward: The typical interface with two methods, which are decorated with OperationContract and a default implementation.

When we know run this example and check the generated WSDL we will get something like this:

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" name="Service1" targetNamespace="http://tempuri.org/">
	<wsdl:types>
		<xsd:schema targetNamespace="http://tempuri.org/Imports">
			<xsd:import schemaLocation="http://localhost:8733/Design_Time_Addresses/SyncWcf/Service1/?xsd=xsd0" namespace="http://tempuri.org/"/>
			<xsd:import schemaLocation="http://localhost:8733/Design_Time_Addresses/SyncWcf/Service1/?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/>
			<xsd:import schemaLocation="http://localhost:8733/Design_Time_Addresses/SyncWcf/Service1/?xsd=xsd2" namespace="http://schemas.datacontract.org/2004/07/SyncWcf"/>
		</xsd:schema>
	</wsdl:types>
	<wsdl:message name="IService1_GetData_InputMessage">
		<wsdl:part name="parameters" element="tns:GetData"/>
	</wsdl:message>
	<wsdl:message name="IService1_GetData_OutputMessage">
		<wsdl:part name="parameters" element="tns:GetDataResponse"/>
	</wsdl:message>
	<wsdl:message name="IService1_GetDataUsingDataContract_InputMessage">
		<wsdl:part name="parameters" element="tns:GetDataUsingDataContract"/>
	</wsdl:message>
	<wsdl:message name="IService1_GetDataUsingDataContract_OutputMessage">
		<wsdl:part name="parameters" element="tns:GetDataUsingDataContractResponse"/>
	</wsdl:message>
	<wsdl:portType name="IService1">
		<wsdl:operation name="GetData">
			<wsdl:input wsaw:Action="http://tempuri.org/IService1/GetData" message="tns:IService1_GetData_InputMessage"/>
			<wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataResponse" message="tns:IService1_GetData_OutputMessage"/>
		</wsdl:operation>
		<wsdl:operation name="GetDataUsingDataContract">
			<wsdl:input wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContract" message="tns:IService1_GetDataUsingDataContract_InputMessage"/>
			<wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContractResponse" message="tns:IService1_GetDataUsingDataContract_OutputMessage"/>
		</wsdl:operation>
	</wsdl:portType>
	<wsdl:binding name="BasicHttpBinding_IService1" type="tns:IService1">
		<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
		<wsdl:operation name="GetData">
			<soap:operation soapAction="http://tempuri.org/IService1/GetData" style="document"/>
			<wsdl:input>
				<soap:body use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal"/>
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="GetDataUsingDataContract">
			<soap:operation soapAction="http://tempuri.org/IService1/GetDataUsingDataContract" style="document"/>
			<wsdl:input>
				<soap:body use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal"/>
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>
	<wsdl:service name="Service1">
		<wsdl:port name="BasicHttpBinding_IService1" binding="tns:BasicHttpBinding_IService1">
			<soap:address location="http://localhost:8733/Design_Time_Addresses/SyncWcf/Service1/"/>
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

Convert to async

To make the service async we only need change the method signature and returing Tasks:

[ServiceContract]
public interface IService1
{
    [OperationContract]
    Task<string> GetData(int value);

    [OperationContract]
    Task<CompositeType> GetDataUsingDataContract(CompositeType composite);

    // TODO: Add your service operations here
}

...

public class Service1 : IService1
{
    public async Task<string> GetData(int value)
    {
        return await Task.FromResult(string.Format("You entered: {0}", value));
    }

    public async Task<CompositeType> GetDataUsingDataContract(CompositeType composite)
    {
        if (composite == null)
        {
            throw new ArgumentNullException("composite");
        }
        if (composite.BoolValue)
        {
            composite.StringValue += "Suffix";
        }

        return await Task.FromResult(composite);
    }
}

When we run this example and check the WSDL we will see that it is (besides some naming that I changed based on my samples) identical:

<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" name="Service1" targetNamespace="http://tempuri.org/">
	<wsdl:types>
		<xsd:schema targetNamespace="http://tempuri.org/Imports">
			<xsd:import schemaLocation="http://localhost:8733/Design_Time_Addresses/AsyncWcf/Service1/?xsd=xsd0" namespace="http://tempuri.org/"/>
			<xsd:import schemaLocation="http://localhost:8733/Design_Time_Addresses/AsyncWcf/Service1/?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/>
			<xsd:import schemaLocation="http://localhost:8733/Design_Time_Addresses/AsyncWcf/Service1/?xsd=xsd2" namespace="http://schemas.datacontract.org/2004/07/AsyncWcf"/>
		</xsd:schema>
	</wsdl:types>
	<wsdl:message name="IService1_GetData_InputMessage">
		<wsdl:part name="parameters" element="tns:GetData"/>
	</wsdl:message>
	<wsdl:message name="IService1_GetData_OutputMessage">
		<wsdl:part name="parameters" element="tns:GetDataResponse"/>
	</wsdl:message>
	<wsdl:message name="IService1_GetDataUsingDataContract_InputMessage">
		<wsdl:part name="parameters" element="tns:GetDataUsingDataContract"/>
	</wsdl:message>
	<wsdl:message name="IService1_GetDataUsingDataContract_OutputMessage">
		<wsdl:part name="parameters" element="tns:GetDataUsingDataContractResponse"/>
	</wsdl:message>
	<wsdl:portType name="IService1">
		<wsdl:operation name="GetData">
			<wsdl:input wsaw:Action="http://tempuri.org/IService1/GetData" message="tns:IService1_GetData_InputMessage"/>
			<wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataResponse" message="tns:IService1_GetData_OutputMessage"/>
		</wsdl:operation>
		<wsdl:operation name="GetDataUsingDataContract">
			<wsdl:input wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContract" message="tns:IService1_GetDataUsingDataContract_InputMessage"/>
			<wsdl:output wsaw:Action="http://tempuri.org/IService1/GetDataUsingDataContractResponse" message="tns:IService1_GetDataUsingDataContract_OutputMessage"/>
		</wsdl:operation>
	</wsdl:portType>
	<wsdl:binding name="BasicHttpBinding_IService1" type="tns:IService1">
		<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
		<wsdl:operation name="GetData">
			<soap:operation soapAction="http://tempuri.org/IService1/GetData" style="document"/>
			<wsdl:input>
				<soap:body use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal"/>
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="GetDataUsingDataContract">
			<soap:operation soapAction="http://tempuri.org/IService1/GetDataUsingDataContract" style="document"/>
			<wsdl:input>
				<soap:body use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal"/>
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>
	<wsdl:service name="Service1">
		<wsdl:port name="BasicHttpBinding_IService1" binding="tns:BasicHttpBinding_IService1">
			<soap:address location="http://localhost:8733/Design_Time_Addresses/AsyncWcf/Service1/"/>
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

Clients

The contract itself is still the same. You can still use the sync-methods on the client side, because WCF doesn’t care (at least with the SOAP binding stuff). It would be clever to also update your client code, but you don’t have to, that was the most important point for us.

Async & OperationContext access

If you are accessing the OperationContext on the server side and using async methods you might stumble on an odd behaviour:

After the first access to OperationContext.Current the value will disappear and OperationContext.Current will be null. This Stackoverflow.com question shows this “bug”.

The reason for this: There are some edge cases, but if you are not using “Reentrant services” the behaviour can be changed with this setting:

<appSettings>
  <add key="wcf:disableOperationContextAsyncFlow" value="false" />
</appSettings>

With this setting if should work like before in the “sync”-world.

Summery

“Async all the things” - even legacy WCF services can be turned into async task based APIs without breaking any clients. Checkout the sample code on GitHub.

Hope this helps!

Links:

Albert Weinert: Microsoft Connect(); 2018 Public Live Streaming

Am 4. Dezember findet die virtuelle Konferenz rund um Visual Studio und Azure statt. Diese startet um 17:30 und die Microsoft Connect(); wird Live im Netz übertragen.

Microsoft Connect(); Parallel zu den Keynotes wird auf meinen Twitch Kanal Live geschaut, gechattet und kommentiert.

Kommt also am 4. Dezember 2018 ab 17:30 Uhr vorbei und wir schauen gemeinsam.

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

Mit Hilfe des Dekorierer Pattern können neue Funktionsblöcke auf Basis bestehender Funktionsblöcke entwickelt werden, ohne das Prinzip der Vererbung überzustrapazieren. In dem folgenden Post werde ich den Einsatz dieses Pattern an Hand eines einfachen Beispiels vorstellen.

Das Beispiel soll für verschiedene Pizzen den Preis (GetPrice()) berechnen. Auch wenn dieses Beispiel keinen direkten Bezug zur Automatisierungstechnik hat, so wird das Grundprinzip des Dekorierer-Pattern recht gut beschrieben. Genauso gut könnten die Pizzen auch durch Pumpen, Zylinder oder Achsen ersetzt werden.

Erste Variante: Der ‚Super-Funktionsblock‘

In dem Beispiel gibt es zwei Grundsorten; American Style und Italian Style. Jede dieser Grundsorten kann mit den Beilagen Salami (Salami), Käse (Cheese) und Brokkoli (Broccoli) versehen werden.

Der naheliegendste Ansatz könnte darin bestehen, die gesamte Funktionalität in einem Funktionsblock zu legen.

Eigenschaften legen die Zusammensetzung der Pizza fest, während eine Methode die gewünschte Berechnung durchführt.

Picture01

Des Weiteren wird FB_init() so erweitert, dass schon bei der Deklaration der Instanzen die Zutaten festgelegt werden. Somit lassen sich verschiedene Pizzavarianten recht einfach erstellen.

fbAmericanSalamiPizza : FB_Pizza(ePizzaStyle := E_PizzaStyle.eAmerican,
                                 bHasBroccoli := FALSE,
                                 bHasCheese := TRUE,
                                 bHasSalami := TRUE);
fbItalianVegetarianPizza : FB_Pizza(ePizzaStyle := E_PizzaStyle.eItalian,
                                    bHasBroccoli := TRUE,
                                    bHasCheese := FALSE,
                                    bHasSalami := FALSE);

Die Methode GetPrice() wertet diese Informationen aus und gibt den geforderten Wert zurück:

METHOD PUBLIC GetPrice : LREAL

IF (THIS^.eStyle = E_PizzaStyle.eItalian) THEN
  GetPrice := 4.5;
ELSIF (THIS^.eStyle = E_PizzaStyle.eAmerican) THEN
  GetPrice := 4.2;
ELSE
  GetPrice := 0;
  RETURN;
END_IF
IF (THIS^.bBroccoli) THEN
  GetPrice := GetPrice + 0.8;
END_IF
IF (THIS^.bCheese) THEN
  GetPrice := GetPrice + 1.1;
END_IF
IF (THIS^.bSalami) THEN
  GetPrice := GetPrice + 1.4;
END_IF

Eigentlich eine ganz solide Lösung. Doch wie so häufig in der Softwareentwicklung, ändern sich die Anforderungen. So kann die Einführung neuer Pizzen, weitere Zutaten erfordern. Der Funktionsblock FB_Pizza wächst kontinuierlich an und somit auch die Komplexität. Auch die Tatsache, dass sich alles in einem Funktionsblock befindet, macht es schwierig die Endwicklung auf mehrere Personen zu verteilen.

Beispiel 1 (TwinCAT 3.1.4022) auf GitHub

Zweite Variante: Die ‚Vererbungshölle‘

Im zweiten Ansatz wird für jede Pizza-Variante ein separater Funktionsblock erstellt. Zusätzlich definiert eine Schnittstelle (I_Pizza) alle gemeinsamen Eigenschaften und Methoden. Da von allen Pizzen der Preis ermittelt werden soll, enthält die Schnittstelle die Methode GetPrice().

Diese Schnittstelle implementieren die beiden Funktionsblöcke FB_PizzaAmericanStyle und FB_PizzaItalianStyle. Somit ersetzen die Funktionsblöcke die Aufzählung E_PizzaStyle und sind die Basis für alle weiteren Pizzen. Die Methode GetPrice() gibt bei diesen beiden FBs den jeweiligen Basispreis zurück.

Darauf aufbauend werden die einzelnen Pizzen, mit den unterschiedlichen Zutaten definiert. So enthält die Pizza Margherita zusätzlich Käse (Cheese) und Tomaten (Tomato). Die Pizza Salami benötigt außerdem noch Salami. Somit erbt der FB für die Pizza Salami von dem FB der Pizza Margherita.

Die Methode GetPrice() greift immer mit dem Super-Zeiger auf die darunter liegende Methode zu und addiert den Betrag für die eigenen Zutaten hinzu, vorausgesetzt diese sind vorhanden.

METHOD PUBLIC GetPrice : LREAL

GetPrice := SUPER^.GetPrice();
IF (THIS^.bSalami) THEN
  GetPrice := GetPrice + 1.4;
END_IF

Daraus ergibt sich eine Vererbungshierarchie, welche die Abhängigkeiten der einzelnen Pizza-Varianten wiederspiegelt.

Picture02

Auch diese Lösung sieht auf den ersten Blick sehr elegant aus. Ein Vorteil ist die gemeinsame Schnittstelle. Jeder Instanz eines der Funktionsblöcke kann somit einem Interface-Pointer vom Typ I_Pizza zugewiesen werden. Dieses ist z.B. bei Methoden hilfreich, da über einen Parameter vom Typ I_Pizza jede Pizza-Variante übergeben werden kann.

Auch können verschiedene Pizzen in ein Array abgelegt und der gemeinsame Preis berechnet werden:

PROGRAM MAIN
VAR
  fbItalianPizzaPiccante     : FB_ItalianPizzaPiccante;
  fbItalianPizzaMozzarella   : FB_ItalianPizzaMozzarella;
  fbItalianPizzaSalami       : FB_ItalianPizzaSalami;
  fbAmericanPizzaCalifornia  : FB_AmericanPizzaCalifornia;
  fbAmericanPizzaNewYork     : FB_AmericanPizzaNewYork;
  aPizza                     : ARRAY [1..5] OF I_Pizza;
  nIndex                     : INT;
  lrPrice                    : LREAL;
END_VAR

aPizza[1] := fbItalianPizzaPiccante;
aPizza[2] := fbItalianPizzaMozzarella;
aPizza[3] := fbItalianPizzaSalami;
aPizza[4] := fbAmericanPizzaCalifornia;
aPizza[5] := fbAmericanPizzaNewYork;

lrPrice := 0;
FOR nIndex := 1 TO 5 DO
  lrPrice := lrPrice + aPizza[nIndex].GetPrice();
END_FOR

Trotzdem weist dieser Ansatz verschiedene Nachteile auf.

Was ist, wenn die Menükarte angepasst und sich die Zusammensetzung einer Pizza dadurch ändert? Angenommen die Pizza Salami soll auch Pilze (Mushroom) erhalten, dann erbt die Pizza Piccante ebenfalls die Pilze, obwohl dieses nicht gewünscht wird. Die gesamte Vererbungshierarchie muss angepasst werden. Durch die festen Beziehungen über die Vererbung, wird die Lösung unflexibel.

Wie kommt das System mit individuellen Kundenwüschen zurecht? Wie z.B. doppelter Käse oder Zutaten, die eigentlich für eine bestimmte Pizza nicht vorgesehen sind.

Befinden sich die Funktionsblöcke in einer Bibliothek, so wären diese Anpassungen nur eingeschränkt möglich.

Vor allen Dingen besteht die Gefahr, dass bestehende Anwendungen, die mit einem älteren Stand der Bibliothek kompiliert wurden, sich nicht mehr korrekt verhalten.

Beispiel 2 (TwinCAT 3.1.4022) auf GitHub

Dritte Variante: Das Dekorierer Pattern

Zur Optimierung der Lösung sind einige Entwurfsprinzipien der objektorientierten Softwareentwicklung hilfreich. Das Einhalten dieser Prinzipen soll helfen, die Softwarestruktur sauber zu halten.

Open Closed Principle

Offen für Erweiterungen: Das bedeutet, dass sich durch die Verwendung von Erweiterungsmodulen die ursprüngliche Funktionalität eines Moduls verändern lässt. Dabei enthalten die Erweiterungsmodule nur die Anpassungen von der ursprünglichen Funktionalität.

Geschlossen für Änderungen: Das bedeutet, dass keine Änderungen des Moduls nötig sind, um es zu erweitern. Das Modul bietet definierte Erweiterungspunkte, über die sich die Erweiterungsmodule anknüpfen lassen.

Identifiziere jene Aspekte, die sich ändern und trenne diese von jenen, die konstant bleiben

Wie werden die Funktionsblöcke aufgeteilt, damit Erweiterungen an möglichst wenigen Stellen notwendig sind?

Bisher wurden die beiden Pizza-Grundsorten American Style und Italian Style durch Funktionsblöcke abgebildet. Warum also nicht auch die Zutaten als Funktionsblöcke definieren? Damit würden wir das Open Closed Principle erfüllen. Unsere Grundsorten und Zutaten sind konstant und somit für Veränderungen geschlossen. Allerdings müssen wir dafür sorgen, dass jede Grundsorte mit beliebigen Zutaten erweitert werden kann. Die Lösung wäre somit offen für Erweiterungen.

Das Dekorierer Pattern verlässt sich beim Erweitern des Verhaltens nicht auf Vererbung. Vielmehr kann jede Beilage auch als Hülle (Wrapper) verstanden werden. Diese Hülle legt sich um ein bereits bestehendes Gericht. Damit dieses möglich ist, implementieren auch die Beilagen das Interface I_Pizza. Jede Beilage enthält des Weiteren ein Interface-Pointer auf die darunterliegende Hülle.

Die Pizza-Grundsorte und die Beilagen werden hierdurch ineinander verschachtelt. Wird die Methode GetPrice() von der äußersten Hülle aufgerufen, so delegiert diese den Aufruf an die darunterliegende Hülle weiter und addiert anschließend seinen Preis hinzu. Das geht solange, bis die Aufrufkette an der Pizza-Grundsorte angekommen ist, welche den Basispreis zurückliefert.

Picture03

Die innerste Hülle gibt ihren Basispreis zurück:

METHOD GetPrice : LREAL

GetPrice := 4.5;

Jede weitere Hülle (Dekorierer) addiert auf die darunterliegende Hülle den gewünschten Aufschlag:

METHOD GetPrice : LREAL

IF (THIS^.ipSideOrder  0) THEN
  GetPrice := THIS^.ipSideOrder.GetPrice() + 0.9;
END_IF

Damit die jeweils darunterliegende Hülle an den Baustein übergeben werden kann, wird die Methode FB_init() um einen zusätzlichen Parameter vom Typ I_Pizza erweitert. Somit werden schon bei der Deklaration der FB-Instanzen die gewünschten Zutaten festgelegt.

METHOD FB_init : BOOL
VAR_INPUT
  bInitRetains	: BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
  bInCopyCode	: BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
  ipSideOrder	: I_Pizza;
END_VAR

THIS^.ipSideOrder := ipSideOrder;

Damit das Durchlaufen der einzelnen Hüllen besser erkennbar wird, habe ich die Methode GetDescription() vorgesehen. Jede Hülle erweitert den bestehenden String um eine kurze Beschreibung.

Picture04

Im folgenden Beispiel, wird die Zusammenstellung der Pizza direkt bei der Deklaration angegeben:

PROGRAM MAIN
VAR
  // Italian Pizza Margherita (via declaration)
  fbItalianStyle : FB_PizzaItalianStyle;
  fbTomato       : FB_DecoratorTomato(fbItalianStyle);
  fbCheese       : FB_DecoratorCheese(fbTomato);
  ipPizza        : I_Pizza := fbCheese;

  fPrice         : LREAL;
  sDescription   : STRING;	
END_VAR

fPrice := ipPizza.GetPrice(); // output: 6.5
sDescription := ipPizza.GetDescription(); // output: 'Pizza Italian Style: - Tomato - Cheese'

Zwischen den Funktionsblöcken besteht keine feste Verbindung. Neue Pizzasorten können definiert werden, ohne dass Veränderungen an bestehenden Funktionsblöcken notwendig sind. Die Vererbungshierarchie legt nicht die Abhängigkeiten zwischen den einzelnen Pizza-Varianten fest.

Picture05

Zusätzlich kann der Interface-Pointer auch per Eigenschaft übergeben werden. Somit ist eine Zusammensetzung oder eine Änderung der Pizza auch zur Laufzeit des Programms möglich.

PROGRAM MAIN
VAR
  // Italian Pizza Margherita (via runtime)
  fbItalianStyle  : FB_PizzaItalianStyle;
  fbTomato        : FB_DecoratorTomato(0);
  fbCheese        : FB_DecoratorCheese(0);
  ipPizza         : I_Pizza;

  bCreate         : BOOL;
  fPrice          : LREAL;
  sDescription    : STRING;
END_VAR

IF (bCreate) THEN
  bCreate := FALSE;
  fbTomato.ipDecorator := fbItalianStyle;
  fbCheese.ipDecorator := fbTomato;
  ipPizza := fbCheese;
END_IF
IF (ipPizza  0) THEN
  fPrice := ipPizza.GetPrice(); // output: 6.5
  sDescription := ipPizza.GetDescription(); // output: 'Pizza Italian Style: - Tomato - Cheese'
END_IF

Auch können in jedem Funktionsblock Besonderheiten eingebaut werden. Dabei kann es sich um zusätzliche Eigenschaften, aber auch um weitere Methoden handeln.

Der Funktionsblock für die Tomaten soll optional auch als Bio-Tomate angeboten werden. Eine Möglichkeit ist natürlich das Anlegen eines neuen Funktionsblocks. Das ist dann notwendig, wenn der vorhandene Funktionsblock nicht erweiterbar ist (z.B. weil er sich in einer Bibliothek befindet). Ist diese Anforderung aber schon vor der ersten Freigabe bekannt, so kann dieses unmittelbar berücksichtigt werden.

Der Funktionsblock erhält in der Methode FB_init() einen zusätzlichen Parameter.

METHOD FB_init : BOOL
VAR_INPUT
  bInitRetains		: BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
  bInCopyCode		: BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
  ipSideOrder		: I_Pizza;
  bWholefoodProduct	: BOOL;
END_VAR

THIS^.ipSideOrder := ipSideOrder;
THIS^.bWholefood := bWholefoodProduct;

Auch dieser Parameter könnte über eine Eigenschaft zur Laufzeit änderbar sein. Bei der Berechnung des Preises wird die Option wie gewünscht berücksichtigt.

METHOD GetPrice : LREAL

IF (THIS^.ipSideOrder  0) THEN
  GetPrice := THIS^.ipSideOrder.GetPrice() + 0.9;
  IF (THIS^.bWholefood) THEN
    GetPrice := GetPrice + 0.3;
  END_IF
END_IF

Eine weitere Optimierung kann die Einführung eines Basis-FB (FB_Decorator) für alle Decorator-FBs sein.

Picture06

Beispiel 3 (TwinCAT 3.1.4022) auf GitHub

Definition

In dem Buch Entwurfsmuster. Elemente wiederverwendbarer objektorientierter Software” von Gamma, Helm, Johnson und Vlissides wird dieses wie folgt ausgedrückt:

„Das Dekorierer Pattern ist eine flexible Alternative zur Unterklassenbildung […], um eine Klasse um zusätzliche Funktionalitäten zu erweitern.”

Implementierung

Der entscheidende Punkt beim Dekorierer Pattern ist, dass beim Erweitern eines Funktionsblock nicht Vererbung zum Einsatz kommt. Soll das Verhalten ergänzt werden, so werden Funktionsblöcke ineinander verschachtelt; sie werden dekoriert.

Zentrale Komponente ist die Schnittstelle IComponent. Diese Schnittstelle implementieren die Funktionsblöcke, die dekoriert werden sollen (Componnent).

Die Funktionsblöcke, die als Dekorierer dienen (Decorator), implementieren ebenfalls die Schnittstelle IComponent. Zusätzlich enthalten diese aber auch eine Referenz (Interface-Pointer component) auf einen weiteren Dekorierer (Decorator) oder auf den Basis-Funktionsblock (Component).

Der äußerste Dekorierer repräsentiert somit den Basis-Funktionsblock, erweitert um die Funktionen der Dekorierer. Die Methode Operation() wird durch alle Funktionsblöcke durchgereicht. Wobei jeder Funktionsblock beliebige Funktionalitäten hinzufügen darf.

Dieser Ansatz bringt einige Vorteile mit sich:

  • Der ursprüngliche Funktionsblock (Component) weiß nichts von den Ergänzungen (Decorator). Es ist nicht notwendig diesen zu erweitern oder anzupassen.
  • Die Dekorierer sind unabhängig voneinander und können auch bei anderen Anwendungen eingesetzt werden.
  • Die Dekorierer können beliebig miteinander kombiniert werden.
  • Ein Funktionsblock kann somit deklarativ oder auch zur Laufzeit sein Verhalten ändern.
  • Ein Client, der über die Schnittstelle IComponent auf den Funktionsblock zugreift, kann auf die gleiche Weise auch mit einem dekorierten Funktionsblock umgehen. Der Client muss nicht angepasst werden; er wird wiederverwendbar.

Aber auch einige Nachteile sind zu beachten:

  • Die Anzahl der Funktionsblöcke kann deutlich zunehmen, Dieses macht die Einarbeitung in eine bestehende Bibliothek aufwendiger.
  • Der Client erkennt nicht, ob es sich um die ursprüngliche Basis-Komponente handelt (wenn über die Schnittstelle IComponent darauf zugegriffen wird), oder ob diese durch Dekorierer erweitert wurden. Das kann ein Vorteil sein (siehe oben), aber auch zu Problemen führen.
  • Durch die langen Aufrufketten wird die Fehlersuche erschwert. Auch können sich die langen Aufrufketten negativ auf die Performanz der Applikation auswirken.

UML Diagramm

Picture07

Bezogen auf das obige Beispiel ergibt sich folgende Zuordnung:

Client MAIN
IComponent I_Pizza
Operation() GetPrice(), GetDescription()
Decorator FB_DecoratorCheese, FB_DecoratorSalami, FB_DecoratorTomato
AddedBehavior() bWholefoodProduct
component ipSideOrder
Component FB_PizzaItalianStyle, FB_PizzaAmericanStyle

Anwendungsbeispiele

Das Dekorierer Pattern ist sehr häufig in Klassen wiederzufinden, die für die Bearbeitung von Datenstreams zuständig sind. Dieses betrifft sowohl die Java Standardbibliothek, als auch das Microsoft .NET Framework.

So gibt es im .NET Framework die Klasse System.IO.Stream. Von dieser Klasse erben u.a. System.IO.FileStream und System.IO.MemoryStream. Beide Unterklassen enthalten aber auch eine Instanz von Stream. Viele Methoden und Eigenschaften von FileStream und MemoryStream greifen auf diese Instanz zu. Man kann auch sagen: Die Unterklassen FileStream und MemoryStream dekorieren Stream.

Weitere Anwendungsfälle sind Bibliotheken zur Erstellung von grafischen Oberflächen. Dazu zählt WPF von Microsoft als auch Swing für Java.

Ein Textfeld (TextBox) und ein Rahmen (Border) werden ineinander verschachtelt; das Textfeld wird mit dem Rahmen dekoriert. Der Rahmen (mit dem Textfeld) wird anschließend an die Page übergeben.

Golo Roden: Babel: Runtime, Polyfill … was wann?

Wer mit Babel eine ES2015-Umgebung simulieren will, braucht entweder @babel/polyfill oder @babel/runtime. Der Polyfill eignet sich für Anwendungen, die Runtime hingegen für Module. Um zu verhindern, dass Code aus der Runtime in jede Datei eingebunden wird, lässt sich das mit @babel/plugin-transform-runtime optimieren.

Jürgen Gutsch: Removing Disqus and adding GitHub Issue Comments

I recently realized that I ran this new blog for almost exactly three years now and wrote almost 100 posts until yet. Running this blog is completely different compared to the the previous one based on the community server on ASP.NET Zone. I now write on markdown files which I commit and push to GitHub. I also switched the language. From January 2007 to November 2015 I wrote in German and since I run this GitHub based blog I switched completely to English, which is a great experience and improves the English writing and speaking skills a lot.

This blog is based on Pretzel, which is a .NET based Jekyll clone, that creates a static website. Pretzel as well as Jekyll is optimized for blogs or similar structured web sites. Both systems take markdown files and turn them based on the Liquid template engine into static HTML pages. This works pretty well and I really like the way to push markdown files to the GitHub repo and get an updated blog a few seconds later on Azure. This is continuous delivery using GitHub and Azure for blog posts. It is amazing. And I really love blogging this way.

Actually the blog is successful from my perspective. Around 6k visits per week is a good number, I guess.

Because the blog is static HTML, at the end I need to extend it with software as a service solutions to create dynamic content or to track the success of that blog.

So I added Disqus to enable comments on this blog. Disqus was quite popular at that time for this kind of blogs and I also get some traffic from Disqus. Anyway, now this service started to show some advertisement on my page and it also shows advertisement that is not really related to the contents of my page.

I also added a small Google AdSense banner to the blog, but this is placed at the end of the page and doesn't really annoy you as a reader, I hope. I put some text upon this banner, to ask you as a reader to support my blog if you like it. A click on that banner doesn't really cost some time or money.

I don't get anything out of the annoying off-topic adds that Disqus shows here, except a free tool to collect blog post comments and store them somewhere outside in the cloud. I don't really "own" the comments, which is the other downside.

Sure Disqus is a free service and someone need to pay for it, but the ownership of the contents is an problem as well as the fact that I cannot influence the contents of the adds displayed on my blog:

Owning the comments

The comments are important contents you provide to me, to the other readers and to the entire developer community. But they are completely separated from the blog post they relate to. They are stored on a different cloud. Actually I have no idea where Disqus stores the comments.

How do I own the comments?

My idea was to use GitHub issues of the blog repository to collect the comments. Every first comment of a blog post should create a GitHub issue and any comment is a comment on this issue. With this solution the actual posts and the comments are in the same repository, they can be linked together and I own this comments a little more than previously.

I already asked on twitter about that and got some positive feedback.

Evaluating a solution

There are already some JavaScript codes available which can be used to add GitHub Issues as comments. The GitHub API is well documented and it should be easy to do this.

I already evaluated a solution to use and decided to go with Utterance

"A lightweight comments widget built on GitHub issues"

Utterance was built by Jeremy Danyow. I stumbled upon it on Jeremys blog post about Using GitHub Issues for Blog Comments. Jeremy works as a Senior Software Engineer at Microsoft, he is member of the Aurelia core team and created also gist.run.

As far as I understood, Utterances is a light weight version of Microsofts comment system used with the new docs on https://docs.microsoft.com. Also Microsoft stores the comments as Issues on GitHub, which is nice because they can create real issues out of it, in case there are real Problems with the docs, etc.

More Links about it: https://utteranc.es/ and https://github.com/utterance.

At the end I just need to add a small HTML snippet to my blog:

<script src="https://utteranc.es/client.js"
        repo="juergengutsch/blog"
        issue-term="title"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

This script will search for Issues with the same title as the current page. If there's no such issue, it will create a new one. If there is such an issue it will create an comment on that issue. This script also supports markdown.

Open questions until yet

Some important open question came up while evaluating the solution:

  1. Is it possible to import all the Disqus comments to GitHub Issues?
    • This is what I need to figure out now.
    • Would be bad to not have the existing comments available in the new system.
  2. What if Jeremys services are not available anymore?

The second question is easy to solve. As I wrote, I will just host the stuff by my own in case Jeremy will shut down his services. The first question is much more essential. It would be cool to get the comments somehow in a readable format. I would than write a small script or a small console app to import the comments as GitHub Issues.

Exporting the Disqus comments to GitHub Issues

Fortunately there is an export feature on Disqus, in the administration settings of the site:

After clicking "Export Comment" the export gets scheduled and you'll get an email with the download link to the export.

The exported file is a GZ compressed XML file including all threads and posts. A thread in this case is an entry per blog post where the comment form was visible. A thread actually doesn't need to contain comments. Post are comments related to a thread. Posts contain the actual comment as message, Author information and relations to the thread and the parent post if it is a reply to a comment.

This is pretty clean XML and it should be easy to import that automatically into GitHub Issues. Now I needed to figure out how the GitHub API works and to write a small C# Script to import all the comments.

This XML also includes the authors names and usernames. This is cool to know, but it doesn't have any value for me anymore, because Disqus users are no GitHub users. I can't set the comments in behalf of real GitHub users. So any migrated comment will be done by myself and I need to mark the comment, that it originally came from another reader.

So it will be something like this:

var message = $@"Comment written by **{post.Author}** on **{post.CreatedAt}**

{post.Message}
";

Importing the comments

I decided to write a small console app and to do some initial tests on a test repo. I extracted the exported data and moved it into the .NET Core console app folder and tried to play around with it.

First I read all threads out of the file and than the posts afterwards. A only selected the threads which are not marked as closed and not marked as deleted. I also checked the blog post URL of the thread, because sometimes the thread was created by a local test run, sometimes I changed the publication date of a post afterwards, which also changed the URL and sometimes the thread was created by a post that was displayed via a proxying page. I tried to filter all that stuff out. The URL need to start with http://asp.net-hacker.rocks or https://asp.net-hacker.rocks to be valid. Also the posts shouldn't be marked as deleted or marked as spam

Than I assigned the posts to the specific threads using the provided thread id and ordered the posts by date. This breaks the dialogues of the Disqus threads, but should be ok for the first step.

Than I created the actual issue post it and posted the assigned comments to the new issue.

That's it.

Reading the XML file is easy using the XmlDocument this is also available in .NET Core:

var doc = new XmlDocument();
doc.Load(path);
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace(String.Empty, "http://disqus.com");
nsmgr.AddNamespace("def", "http://disqus.com");
nsmgr.AddNamespace("dsq", "http://disqus.com/disqus-internals");

IEnumerable<Thread> threads = await FindThreads(doc, nsmgr);
IEnumerable<Post> posts = FindPosts(doc, nsmgr);

Console.WriteLine($"{threads.Count()} valid threads found");
Console.WriteLine($"{posts.Count()} valid posts found");

I need to use the XmlNamespaceManager here to use tags and properties using the Disqus namespaces. The XmlDocument as well as the XmlNamespaceManager need to get passed into the read methods then. The two find methods are than reading the threads and posts out of the XmlDocument.

In the next snippet I show the code to read the threads:

private static async Task<IEnumerable<Thread>> FindThreads(XmlDocument doc, XmlNamespaceManager nsmgr)
{
    var xthreads = doc.DocumentElement.SelectNodes("def:thread", nsmgr);

    var threads = new List<Thread>();
    var i = 0;
    foreach (XmlNode xthread in xthreads)
    {
        i++;

        long threadId = xthread.AttributeValue<long>(0);
        var isDeleted = xthread["isDeleted"].NodeValue<bool>();
        var isClosed = xthread["isClosed"].NodeValue<bool>();
        var url = xthread["link"].NodeValue();
        var isValid = await CheckThreadUrl(url);

        Console.WriteLine($"{i:###} Found thread ({threadId}) '{xthread["title"].NodeValue()}'");

        if (isDeleted)
        {
            Console.WriteLine($"{i:###} Thread ({threadId}) was deleted.");
            continue;
        }
        if (isClosed)
        {
            Console.WriteLine($"{i:###} Thread ({threadId}) was closed.");
            continue;
        }
        if (!isValid)
        {
            Console.WriteLine($"{i:###} the url Thread ({threadId}) is not valid: {url}");
            continue;
        }

        Console.WriteLine($"{i:###} Thread ({threadId}) is valid");
        threads.Add(new Thread(threadId)
        {
            Title = xthread["title"].NodeValue(),
            Url = url,
            CreatedAt = xthread["createdAt"].NodeValue<DateTime>()

        });
    }

    return threads;
}

I think there's nothing magic in it. Even assigning the posts to the threads is just some LINQ code.

To create the actual issues and comments, I use the Octokit.NET library which is available on NuGet and GitHub.

dotnet add package Octokit

This library is quite simple to use and well documented. You have the choice between basic authentication and token authentication to connect to GitHub. I chose the token authentication which is the proposed way to connect. To get the token you need to go to the settings of your GitHub account. Choose a personal access token and specify the rights the for the token. The basic rights to contribute to the specific repository are enough in this case:

private static async Task PostIssuesToGitHub(IEnumerable<Thread> threads)
{
    var client = new GitHubClient(new ProductHeaderValue("DisqusToGithubIssues"));
    var tokenAuth = new Credentials("secret personal token from github");
    client.Credentials = tokenAuth;

    var issues = await client.Issue.GetAllForRepository(repoOwner, repoName);
    foreach (var thread in threads)
    {
        if (issues.Any(x => !x.ClosedAt.HasValue && x.Title.Equals(thread.Title)))
        {
            continue;
        }

        var newIssue = new NewIssue(thread.Title);
        newIssue.Body = $@"Written on {thread.CreatedAt} 

URL: {thread.Url}
";

        var issue = await client.Issue.Create(repoOwner, repoName, newIssue);
        Console.WriteLine($"New issue (#{issue.Number}) created: {issue.Url}");
        await Task.Delay(1000 * 5);

        foreach (var post in thread.Posts)
        {
            var message = $@"Comment written by **{post.Author}** on **{post.CreatedAt}**

{post.Message}
";

            var comment = await client.Issue.Comment.Create(repoOwner, repoName, issue.Number, message);
            Console.WriteLine($"New comment by {post.Author} at {post.CreatedAt}");
            await Task.Delay(1000 * 5);
        }
    }
}

This method gets the list of Disqus threads, creates the GitHub client and inserts one thread by another. I also read the existing Issues from GitHub in case I need to run the migration twice because of an error. After the Issue is created, I only needed to create the comments per Issue.

After I started that code, the console app starts to add issues and comments to GitHub:

The comments are set as expected:

Unfortunately the import breaks after a while with a weird exception.

Octokit.AbuseException

Unfortunately that run didn't finish. After the first few issues were entered I got an exception like this.

Octokit.AbuseException: 'You have triggered an abuse detection mechanism and have been temporarily blocked from content creation. Please retry your request again later.'

This Exception happens because I reached the creation rate limit (user.creation_rate_limit_exceeded). This limit is set by GitHub on the public API. It is not allowed to do more than 5000 requests per hour: https://developer.github.com/v3/#rate-limiting

You can see such security related events in the security tap of your GitHub account settings.

There is no real solution to solve this problem, except to add more checks and fallbacks to the migration code. I checked which issue already exists and migrate only the issues that don't exist. I also added a five second delay between each request to GitHub. This only increases the migration time, and helps to start the migration only two times. Without the delay I got the exception more often during the tests.

Using Utterances

Once the Issues are migrated to GutHub, I need to use Utterances to the blog. At first you need to install the utterances app on your repository. The repository needs to be public and the issues should be enabled obviously.

On https://utteranc.es/ there is a kind of a configuration wizard that creates the HTML snippet for you, which you need to add to your blog. In my case it is the small snippet I already showed previously:

<script src="https://utteranc.es/client.js"
        repo="juergengutsch/blog"
        issue-term="title"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

This loads the Uttereances client script, configures my blog repository and the way the issued will be found in my repository. You have different options for the issue-term. Since I set the blog post title as GitHub issue title, I need to tell Utterances to look at the tile. The theme I want to use here is the GitHub light theme. The dark theme doesn't fit the blog style. I was also able to override the CSS by overriding the following two CSS classes:

.utterances {}
.utterances-frame {}

The result

At the end it worked pretty cool. After the migration and after I changed the relevant blog template I tried it locally using the pretzel taste command.

If you want to add a comment as a reader, you need to logon with your GitHub account and you need to grand the utterances app to post to my repo with our name.

Not every new commend will be stored in the repository of my blog. All the contents are in the same repository. There will be an issue per post, so it is almost directly linked.

What do you think? Do you like it? Tell me about your opinion :-)

BTW: You will find the migration tool on GitHub.

Stefan Henneken: IEC 61131-3: The ‘State’ Pattern

State machines are used regularly, especially in automation technology. The state pattern provides an object-oriented approach that offers important advantages especially for larger state machines.

Most developers have already implemented state machines in IEC 61131-3: one consciously, the other one perhaps unconsciously. The following is a simple example of three different approaches:

  1. CASE statement
  2. State transitions in methods
  3. The ‘state’ pattern

Our example describes a vending machine that dispenses a product after inserting a coin and pressing a button. The number of products is limited. If a coin is inserted and the button is pressed although the machine is empty, the coin is returned.

The vending machine shall be mapped by the function block FB_Machine. Inputs accept the events and the current state and the number of still available products are read out via outputs. The declaration of the FB defines the maximum number of products.

FUNCTION_BLOCK PUBLIC FB_Machine
VAR_INPUT
  bButton : BOOL;
  bInsertCoin : BOOL;
  bTakeProduct : BOOL;
  bTakeCoin : BOOL;
END_VAR
VAR_OUTPUT
  eState : E_States;
  nProducts : UINT;
END_VAR

UML state diagram

State machines can be very well represented as a UML state diagram.

Picture01

A UML state diagram describes an automaton that is in exactly one state of a finite set of states at any given time.

The states in a UML state diagram are represented by rectangles with rounded corners (vertices) (in other diagram forms also often as a circle). States can execute activities, e.g. when entering the state (entry) or when leaving the state (exit). With entry / n = n – 1, the variable n is decremented when entering the state.

The arrows between the states symbolize possible state transitions. They are labeled with the events that lead to the respective state transition. A state transition occurs when the event occurs and an optional condition (guard) is fulfilled. Conditions are specified in square brackets. This allows decision trees to be implemented.

First variant: CASE statement

You will often find CASE statements for the conversion of state machines. The CASE statement queries every possible state. The conditions are queried for the individual states within the respective areas. If the condition is fulfilled, the action is executed and the state variable is adapted. To increase readability, the state variable is often mapped as ENUM.

TYPE E_States :
(
  eWaiting := 0,
  eHasCoin,
  eProductEjected,
  eCoinEjected
);
END_TYPE

Thus, the first variant of the state machine looks like this:

FUNCTION_BLOCK PUBLIC FB_Machine
VAR_INPUT
  bButton             : BOOL;
  bInsertCoin         : BOOL;
  bTakeProduct        : BOOL;
  bTakeCoin           : BOOL;
END_VAR
VAR_OUTPUT
  eState              : E_States;
  nProducts           : UINT;
END_VAR
VAR
  rtrigButton         : R_TRIG;
  rtrigInsertCoin     : R_TRIG;
  rtrigTakeProduct    : R_TRIG;
  rtrigTakeCoin       : R_TRIG;
END_VAR

rtrigButton(CLK := bButton);
rtrigInsertCoin(CLK := bInsertCoin);
rtrigTakeProduct(CLK := bTakeProduct);
rtrigTakeCoin(CLK := bTakeCoin);
 
CASE eState OF
  E_States.eWaiting:
    IF (rtrigButton.Q) THEN
      ; // keep in the state
    END_IF
    IF (rtrigInsertCoin.Q) THEN
      ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has insert a coin.', '');
      eState := E_States.eHasCoin;
    END_IF
 
  E_States.eHasCoin:
    IF (rtrigButton.Q) THEN
      IF (nProducts > 0) THEN
        nProducts := nProducts - 1;
        ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has pressed the button. Output product.', '');
        eState := E_States.eProductEjected;
      ELSE
        ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has pressed the button. No more products. Return coin.', '');
        eState := E_States.eCoinEjected;
      END_IF
    END_IF
 
  E_States.eProductEjected:
    IF (rtrigTakeProduct.Q) THEN
      ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has taken the product.', '');
      eState := E_States.eWaiting;
    END_IF
 
  E_States.eCoinEjected:
    IF (rtrigTakeCoin.Q) THEN
      ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has taken the coin.', '');
      eState := E_States.eWaiting;
    END_IF
 
  ELSE
    ADSLOGSTR(ADSLOG_MSGTYPE_ERROR, 'Invalid state', '');
    eState := E_States.eWaiting;
END_CASE

A quick test shows that the FB does what it is supposed to do:

Picture02

However, it quickly becomes clear that larger applications cannot be implemented in this way. The clarity is completely lost after a few states.

Sample 1 (TwinCAT 3.1.4022) on GitHub

Second variant: State transitions in methods

The problem can be reduced if all state transitions are implemented as methods.

Picture03

If a particular event occurs, the respective method is called.

FUNCTION_BLOCK PUBLIC FB_Machine
VAR_INPUT
  bButton             : BOOL;
  bInsertCoin         : BOOL;
  bTakeProduct        : BOOL;
  bTakeCoin           : BOOL;
END_VAR
VAR_OUTPUT
  eState              : E_States;
  nProducts           : UINT;
END_VAR
VAR
  rtrigButton         : R_TRIG;
  rtrigInsertCoin     : R_TRIG;
  rtrigTakeProduct    : R_TRIG;
  rtrigTakeCoin       : R_TRIG;
END_VAR

rtrigButton(CLK := bButton);
rtrigInsertCoin(CLK := bInsertCoin);
rtrigTakeProduct(CLK := bTakeProduct);
rtrigTakeCoin(CLK := bTakeCoin);
 
IF (rtrigButton.Q) THEN
  THIS^.PressButton();
END_IF
IF (rtrigInsertCoin.Q) THEN
  THIS^.InsertCoin();
END_IF
IF (rtrigTakeProduct.Q) THEN
  THIS^.CustomerTakesProduct();
END_IF
IF (rtrigTakeCoin.Q) THEN
  THIS^.CustomerTakesCoin();
END_IF

Depending on the current state, the desired state transition is executed in the methods and the state variable is adapted:

METHOD INTERNAL CustomerTakesCoin : BOOL
IF (THIS^.eState = E_States.eCoinEjected) THEN
  ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has taken the coin.', '');
  eState := E_States.eWaiting;
END_IF
 
METHOD INTERNAL CustomerTakesProduct : BOOL
IF (THIS^.eState = E_States.eProductEjected) THEN
  ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has taken the product.', '');
  eState := E_States.eWaiting;
END_IF
 
METHOD INTERNAL InsertCoin : BOOL
IF (THIS^.eState = E_States.eWaiting) THEN
  ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has insert a coin.', '');
  THIS^.eState := E_States.eHasCoin;
END_IF
 
METHOD INTERNAL PressButton : BOOL
IF (THIS^.eState = E_States.eHasCoin) THEN
  IF (THIS^.nProducts > 0) THEN
    THIS^.nProducts := THIS^.nProducts - 1;
    ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has pressed the button. Output product.', '');
    THIS^.eState := E_States.eProductEjected;
  ELSE                
    ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Customer has pressed the button. No more products. Return coin.', '');
    THIS^.eState := E_States.eCoinEjected;
  END_IF
END_IF

This approach also works perfectly. However, the state machine remains in only one function block. Although the state transitions are shifted to methods, this is a solution approach of structured programming. This still ignores the possibilities of object orientation. This leads to the result that the source code is still difficult to extend and is illegible.

Sample 2 (TwinCAT 3.1.4022) on GitHub

Third variant: The state pattern

Some OO design principles are helpful for the implementation of the State Pattern:

Cohesion (= degree to which a class has a single concentrated purpose) and delegation

Encapsulate each responsibility into a separate object and delegate calls to these objects. One class, one responsibility!

Identify those aspects that change and separate them from those that remain constant

How are the objects split so that extensions to the state machine are necessary in as few places as possible? Previously, FB_Machine had to be adapted for each extension. This is a major disadvantage, especially for large state machines on which several developers are working.

Let’s look again at the methods CustomerTakesCoin(), CustomerTakesProduct(), InsertCoin() and PressButton(). They all have a similar structure. In IF statements, the current state is queried and the desired actions are executed. If necessary, the current state is also adjusted. However, this approach does not scale. Each time a new state is added, several methods have to be adjusted.

The state pattern scatters the status to several objects. Each possible status is represented by a FB. These status FBs contain the entire behavior for the respective state. Thus, a new status can be introduced without having to change the source code of the original blocks.

Every action (CustomerTakesCoin(), CustomerTakesProduct(), InsertCoin(), and PressButton()) can be executed on any state. Thus, all status FBs have the same interface. For this reason, one interface is introduced for all status FBs:

Picture04

FB_Machine aggregates this interface (line 9), which delegates the method calls to the respective status FBs (lines 30, 34, 38 and 42).

FUNCTION_BLOCK PUBLIC FB_Machine
VAR_INPUT
  bButton            : BOOL;
  bInsertCoin        : BOOL;
  bTakeProduct       : BOOL;
  bTakeCoin          : BOOL;
END_VAR
VAR_OUTPUT
  ipState            : I_State := fbWaitingState;
  nProducts          : UINT;
END_VAR
VAR
  fbCoinEjectedState    : FB_CoinEjectedState(THIS);
  fbHasCoinState        : FB_HasCoinState(THIS);
  fbProductEjectedState : FB_ProductEjectedState(THIS);
  fbWaitingState        : FB_WaitingState(THIS);
 
  rtrigButton           : R_TRIG;
  rtrigInsertCoin       : R_TRIG;
  rtrigTakeProduct      : R_TRIG;
  rtrigTakeCoin         : R_TRIG;
END_VAR
 
rtrigButton(CLK := bButton);
rtrigInsertCoin(CLK := bInsertCoin);
rtrigTakeProduct(CLK := bTakeProduct);
rtrigTakeCoin(CLK := bTakeCoin);
 
IF (rtrigButton.Q) THEN
  ipState.PressButton();
END_IF
 
IF (rtrigInsertCoin.Q) THEN
  ipState.InsertCoin();
END_IF
 
IF (rtrigTakeProduct.Q) THEN
  ipState.CustomerTakesProduct();
END_IF
 
IF (rtrigTakeCoin.Q) THEN
  ipState.CustomerTakesCoin();
END_IF

But how can the status be changed in the respective methods, the individual status FBs?

First of all, an instance within FB_Machine is declared by each status FB. Via FB_init(), a pointer to FB_Machine is transferred to each status FB (lines 13 – 16).

Each single instance can be read by property from FB_Machine. Each time an interface pointer to I_State is returned.

Picture05

Furthermore, FB_Machine receives a method for setting the status,

METHOD INTERNAL SetState : BOOL
VAR_INPUT
  newState : I_State;
END_VAR
THIS^.ipState := newState;

and a method for changing the current number of products:

METHOD INTERNAL SetProducts : BOOL
VAR_INPUT
  newProducts : UINT;
END_VAR
THIS^.nProducts := newProducts;

FB_init() receives another input variable, so that the maximum number of products can be specified in the declaration.

Since the user of the state machine only needs FB_Machine and I_State, the four properties (CoinEjectedState, HasCoinState, ProductEjectedState and WaitingState), the two methods (SetState() and SetProducts()) and the four status FBs (FB_CoinEjectedState(), FB_HasCoinState(), FB_ProductEjectedState() and FB_WaitingState()) were declared as INTERNAL. If the FBs of the state machine are in a compiled library, they are not visible from the outside. These are also not present in the library repository. The same applies to elements that are declared as PRIVATE. FBs, interfaces, methods and properties that are only used within a library, can thus be hidden from the user of the library.

The test of the state machine is the same in all three variants:

PROGRAM MAIN
VAR
  fbMachine      : FB_Machine(3);
  sState         : STRING;
  bButton        : BOOL;
  bInsertCoin    : BOOL;
  bTakeProduct   : BOOL;
  bTakeCoin      : BOOL;
END_VAR
 
fbMachine(bButton := bButton,
          bInsertCoin := bInsertCoin,
          bTakeProduct := bTakeProduct,
          bTakeCoin := bTakeCoin);
sState := fbMachine.ipState.Description;
 
bButton := FALSE;
bInsertCoin := FALSE;
bTakeProduct := FALSE;
bTakeCoin := FALSE;

The statement in line 15 is intended to simplify testing, since a readable text is displayed for each state.

Sample 3 (TwinCAT 3.1.4022) on GitHub

This variant seems quite complex at first sight, since considerably more FBs are needed. But the distribution of responsibilities to single FBs makes this approach very flexible and much more robust for extensions.

This becomes clear when the individual status FBs become very extensive. For example, a state machine could control a complex process in which each status FB contains further subprocesses. A division into several FBs makes such a program maintainable in the first place, especially if several developers are involved.

For very small state machines, the use of the state pattern is not necessarily the most optimal variant. I myself also like to fall back on the solution with the CASE statement.

Alternatively, IEC 61131-3 offers a further option for implementing state machines with the Sequential Function Chart (SFC). But that is another story.

Definition

In the book “Design patterns: elements of reusable object-oriented software” by Gamma, Helm, Johnson and Vlissides, this is expressed as follows:

Allow an object to change its behavior when its internal state changes. It will look as if the object has changed its class.

Implementation

A common interface (State) is defined, which contains a method for each state transition. For each state, a class is created that implements this interface (State1, State2, …). As all states have the same interface, they are interchangeable.

Such a state object is aggregated (encapsulated) by the object whose behavior has to be changed depending on the state (Context). This object represents the current internal state (currentState) and encapsulates the state-dependent behavior. The context delegates calls to the currently set status object.

The state changes can be performed by the specific state objects themselves. To do this, each status object requires a reference to the context (Context). The context must also provide a method for changing the state (setState()). The subsequent state is passed to the method setState() as a parameter. For this purpose, the context offers all possible states as properties.

UML Diagram

Picture06

Based on the example above, the following assignment results:

Context FB_Machine
State I_State
State1, State2, … FB_CoinEjectedState, FB_HasCoinState,
FB_ProductEjectedState, FB_WaitingState
Handle() CustomerTakesCoin(), CustomerTakesProduct(),
InsertCoin(), PressButton()
GetState1, GetState2, … CoinEjectedState, HasCoinState,
ProductEjectedState, WaitingState
currentState ipState
setState() SetState()
context pMachine

Application examples

A TCP communication stack is a good example of using the state pattern. Each state of a connection socket can be represented by corresponding state classes (TCPOpen, TCPClosed, TCPListen, …). Each of these classes implements the same interface (TCPState). The context (TCPConnection) contains the current state object. All actions are transferred to the respective state class via this state object. This class processes the actions and changes to a new state if necessary.

Text parsers are also state-based. For example, the meaning of a character usually depends on the previously read characters.

Jürgen Gutsch: Disabling comments on this blog until they are moved to GitHub

I'm going to remove the Disqus comments on this blog and move to GitHib issue based comments. The reason is, that I don't want to have advertisements that are not related to the contents of this page. Another reason is, that I want to have the full control over the comments. The third reason is related to GDPR: I've no Idea yet what Disqus is doing to protect the users privacy and how the users are able control their personal data. With the advertisements they are displaying it gets less transparent, because I don't know who what is the original source of the adds and who is responsible for the users personal data.

I removed Disqus from my blog

I'm currently migrating all the Disqus comments to GitHub issues. There will be an GitHub issue per blog post and the issue comments will be the blog post comments than. I will lose the dialogue hierarchy of the comments, but this isn't really needed. Another downside for you readers is, that they will need to have an GiHub account to create comments. Otherwise the most of you already have one and you don't need to have an Discus account anymore to drop a comment.

To do the migration I removed Disqus first and exported all the comments. After a few days of migrating and testing I'll enable the GitHub issue comments on my blog. There will be a comment form on on each blog post as usual and you don't need to go to GitHub to drop a comment.

I will write a detailed blog post about the new comment system and how I migrated it, if it's done.

The new GitHub issue based comments should be available after the weekend

Norbert Eder: Canary Deployment

In Blue Green Deployment habe ich einen Ansatz beschrieben, wie neue Releases in Produktivumgebungen vor der Aktivierung getestet werden können. Daraus lässt sich mit höherer Wahrscheinlichkeit auf die Funktionsfähigkeit eines Releases rückschließen. Allerdings wird nur getestet. Wie stabil und performant die Software läuft, kann nicht beurteilt werden. Eine Hilfe stellen Canary Deployments dar.

Canary Deployment (Kanarienvogel) hat den namentlichen Ursprung in den alten Kohleminen. Als Frühwarnsystem vor giftigen Gasen, haben die Minenarbeiter Kanarienvögel in Käfigen aufgestellt. Traten giftige Gase aus, sind die Kanarienvögel gestorben und die Arbeiter konnten sich noch schnell in Sicherheit bringen.

Wie funktioniert aber nun ein Canary Deployment?

Es gibt – wie auch beim Blue Green Deployment – zumindest zwei Produktivsysteme. Eines der beiden System (oder Teile davon) erhalten Updates. Nun kann der aktualisierte Part getestet werden (sowohl automatisiert, als auch manuell). Zudem wird ein zuvor definierter Teil des Traffics über das aktualisierte System geleitet.

Canary Deployment | Norbert Eder

Canary Deployment

Durch sukzessives Umleiten und Belasten des neuen Systems, werden aussagekräftige Hinweise über die Funktionsfähigkeit (auch unter Last) gegeben.

Ein Beispiel: Es wird festgelegt, dass nach der Aktualisierung, 2% des Traffics über das neue System geleitet werden. Treten keine Probleme auf, kann der Anteil erhöht werden. Treten Probleme auf, sind maximal 2% der Benutzer davon betroffen. Ein Rollback ist sofort möglich.

Mit diesem Aufbau steht also ein Frühwarnsystem zur Verfügung. Wir erhalten mehr Sicherheit und bei Problemen ist nur ein Bruchteil der Benutzer betroffen.

Einher geht allerdings auch ein infrastruktureller Aufwand und eine erhöhte Komplexität.

Der Beitrag Canary Deployment erschien zuerst auf Norbert Eder.

Norbert Eder: Blue Green Deployment

Viele Entwickler setzen mittlerweile auf die Unterstützung von automatisierten Tests und gewährleisten dadurch ein frühe Fehlererkennung, geringere Kosten bei der Behebung und schlussendlich eine hohe Qualität. Dennoch können Fehler nicht vollkommen ausgeschlossen werden.

Einer der Gründe hierfür ist, dass die Tests in der Regel nur in Testsystemen ausgeführt werden. Somit ist eine Aussage hinsichtlich der Funktionsweise im Produktivsystem nicht gegeben. Anwender überraschen uns Entwickler gerne mit unkonventionellen Eingaben oder einer eigenwilligen Bedienung der Software. Dies kann unter Umständen zu schiefen Datenständen führen. Was also in der Entwicklungs- bzw. Testumgebung funktioniert, muss dies noch lange nicht in der Produktivumgebung tun. Was kann man nun unternehmen, um eine bessere Aussage treffen zu können?

Eine Möglichkeit besteht im Blue Green Deployment. Dabei besteht das Produktivsystem zweimal. Einmal als blaue, einmal als grüne Linie. Aktiv ist immer nur eines der beiden Systeme. Das inaktive System kann für Tests herangezogen werden. Dabei können die Systeme auf unterschiedlicher (aber ähnlicher) Hardware oder VMs laufen.

Ein neues Release wird dabei immer am inaktiven System eingespielt und getestet. Sind alle Tests erfolgreich und stehen alle Funktionen zur Verfügung, wird das inaktive zum aktiven System und umgekehrt. In anderen Worten: War das blaue System aktiv und das grüne System inaktiv, dann erhielt das grüne System das Update und wurde nach erfolgreichen Tests aktiv. Nun ist das blaue System inaktiv und erhält das nächste kommende Update.

Dies bietet natürlich auch noch weitere Vorteile. So ist es sehr schnell möglich, wieder auf die alte Version zurückzugehen (Rollback). Zudem steht ein zweites System bei Ausfällen (Hardware etc.) zur Verfügung.

Die zusätzliche Sicherheit bringt jedoch einige Herausforderungen hinsichtlich Infrastruktur, Deploymentprozess, aber auch der Entwicklung (z.B. Umgang mit Schemaänderungen an der Datenbank) mit sich. Belohnt wird man durch eine höhere Ausfallssicherheit und einer möglichen (verbesserten) Aussage über die Funktionsfähigkeit eines neuen Releases im Produktivsystem.

Darauf aufbauend kann ein Canary Deployment eine noch bessere Aussagekraft im Produktiveinsatz geben.

Credit: Server-Icon von FontAwesome / CC Attribution 4.0 International, alle anderen Icons von Microsoft Powerpoint.

Der Beitrag Blue Green Deployment erschien zuerst auf Norbert Eder.

Jürgen Gutsch: Customizing ASP.​NET Core Part 10: TagHelpers

This was initially planned as the last topic of this series, because this also was the last part of the talk about customizing ASP.NET Core I did in the past. See the initial post about this series. Now I have three additional customizing topics to talk about. If you like to propose another topic feel free to drop a comment in the initial post.

In this tenth part of this series I'm going to write about TagHelpers. The built in TagHelpers are pretty useful and making the razor more pretty and more readable. Creating custom TagHelpers will make your life much easier.

This series topics

About TagHelpers

With TagHelpers you are able to extend existing HTML tags or to create new tags that get rendered on the server side. The extensions or the new tags are not visible in the browsers. TagHelpers a only kind of shortcuts to write easier and less HTML or Razor code on the server side. TagHelpers wil be interpreted on the server and will produce "real" HTML code for the browsers.

TagHelpers are not a new thing in ASP.NET Core, it was there since the first version of ASP.NET Core. The most existing and built-in TagHelpers are a replacement for the old fashioned HTML Helpers, which are still existing and working in ASP.NET Core to keep the Razor views compatible to ASP.NET Core.

A very basic example of extending HTML tags is the built in AnchorTagHelper:

<!-- old fashioned HtmlHelper -->
<li>@Html.Link("Home", "Index", "Home")</li>
<!-- new TagHelper -->
<li><a asp-controller="Home" asp-action="Index">Home</a></li>

The HtmlHelper are kinda strange between the HTML tags, for HTML developers. It is hard to read. It is kind of disturbing and interrupting while reading the code. It is maybe not for ASP.NET Core developers who are used to read that kind of code. But compared to the TagHelpers it is really ugly. The TagHelpers feel more natural and more like HTML even if they are not and even if they are getting rendered on the server.

Many of the HtmlHelper can be replaced with a TagHelper.

There are also some new tags built with TagHelpers. Tags that are not existing in HTML, but look like HTML. One example is the EnvironmentTagHelper:

<environment include="Development">
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
    <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
            asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
            asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>

This TagHelper renders or doesn't render the contents depending of the current runtime environment. In this case the target environment is the development mode. The first environment tag renders the contents if the current runtime environment is set to Development and the second one renders the contents if it not set to Development. This makes it a useful helper to render debugable scripts or styles in Development mode and minified and optimized code in any other runtime environment.

Creating custom TagHelpers

Just as a quick example, let's assume we need to have any tag configurable as bold and colored in a specific color:

<p strong color="red">Use this area to provide additional information.</p>

This looks like pretty old fashioned HTML out of the nineties, but this is just to demonstrate a simple TagHelper. But this can be done by a TagHelper that extend any tag that has an attribute called strong

[HtmlTargetElement(Attributes = "strong")]
public class StrongTagHelper : TagHelper
{
    public string Color { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.Attributes.RemoveAll("strong");

        output.Attributes.Add("style", "font-weight:bold;");
        if (!String.IsNullOrWhiteSpace(Color))
        {
            output.Attributes.RemoveAll("style");
            output.Attributes.Add("style", $"font-weight:bold;color:{Color};");
        }
    }
}

The first line tells the tag helper to work on tags with an target attribute strong. This TagHelper doesn't define an own tag. But also provides an additional attribute to specify the color. At least the Process method defined how to render the HTML to the output stream. In this case it adds some CSS inline Styles to the current tag. It also removes the target attribute from the current tag. The color attribute won't show up.

This will look like this

<p color="red">Use this area to provide additional information.</p>

The next sample show how to define a custom tag using a TagHelper:

public class GreeterTagHelper : TagHelper
{
    [HtmlAttributeName("name")]
    public string Name { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "p";
        output.Content.SetContent($"Hello {Name}");
    }
}

This TagHelper handles a greeter tag that has a property name. In the Process method the current tag will be changed to a p tag and the new content is set the the current output.

<greeter name="Readers"></greeter>

The result is like this:

<p>Hello Readers</p>

A more complex scenario

The TagHelpers in the last section were pretty basic just to show how TagHelpers work. The next sample is a little more complex and shows an almost real scenario. This TagHelper renders a table with a list of items. This is a generic TagHelper and shows a real reason to create own custom TagHelpers. With this you are able to reuse an a isolated piece of view code. You can wrap for example Bootstrap components to make it much easier to use, e.g. with just one tag instead of nesting five levels of div tags. Or you can just simplify your Razor views:

public class DataGridTagHelper : TagHelper
{
    [HtmlAttributeName("Items")]
    public IEnumerable<object> Items { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "table";
        output.Attributes.Add("class", "table");
        var props = GetItemProperties();

        TableHeader(output, props);
        TableBody(output, props);
    }

    private void TableHeader(TagHelperOutput output, PropertyInfo[] props)
    {
        output.Content.AppendHtml("<thead>");
        output.Content.AppendHtml("<tr>");
        foreach (var prop in props)
        {
            var name = GetPropertyName(prop);
            output.Content.AppendHtml($"<th>{name}</th>");
        }
        output.Content.AppendHtml("</tr>");
        output.Content.AppendHtml("</thead>");
    }

    private void TableBody(TagHelperOutput output, PropertyInfo[] props)
    {
        output.Content.AppendHtml("<tbody>");
        foreach (var item in Items)
        {
            output.Content.AppendHtml("<tr>");
            foreach (var prop in props)
            {
                var value = GetPropertyValue(prop, item);
                output.Content.AppendHtml($"<td>{value}</td>");
            }
            output.Content.AppendHtml("</tr>");
        }
        output.Content.AppendHtml("</tbody>");
    }

    private PropertyInfo[] GetItemProperties()
    {
        var listType = Items.GetType();
        Type itemType;
        if (listType.IsGenericType)
        {
            itemType = listType.GetGenericArguments().First();
            return itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        }
        return new PropertyInfo[] { };
    }

    private string GetPropertyName(PropertyInfo property)
    {
        var attribute = property.GetCustomAttribute<DisplayNameAttribute>();
        if (attribute != null)
        {
            return attribute.DisplayName;
        }
        return property.Name;
    }

    private object GetPropertyValue(PropertyInfo property, object instance)
    {
        return property.GetValue(instance);
    }
}

To use this TagHelper you just need to assign a list of items to this tag:

<data-grid persons="Model.Persons"></data-grid>

In this case it is a list of persons, that we get in the Persons property of our current model. The Person class I use here looks like this:

public class Person
{
    [DisplayName("First name")]
    public string FirstName { get; set; }
    
    [DisplayName("Last name")]
    public string LastName { get; set; }
    
    public int Age { get; set; }
    
    [DisplayName("Email address