Senior Software Architect und Technology Advisor
Syrian Hadad
Wenn neue Technologie auf reale Anforderungen trifft
27. Juni 2017
.NET Core im Praxistest
400
Rechenzentrum Softwareentwicklung
Softwarelösungen im kantonalen Umfeld und für den Bund
…
1Lessons learned20’
Warum .NET Core?15’ 2 Die ersten Projekte
45’
3
Fragen und Antworten5Tipps & Tricks20’4
WARUM .NET CORE?
.NET Fairytale
.NET Fairytale
Es war einmal eine einfache, Windows-basierte Welt mit .NET
Linux-Freunde haben im 2004 veröffentlicht, es fristete jedoch lange nur ein Schattendasein
.NET für Windows aber entwickeltesich Jahr für Jahr munter weiter
Und wenn .NET nicht gestorben ist, dann lebt es noch heute…
Oder doch nicht?
• Halt, die Geschichte verlief irgendwie anders…
Es gab zwei Pfade…
j-photo-u1
Windows-onlyCross-plattform
Da gab es doch noch eine Liebeserklärung…
Es musste sich weiterentwickeln, aber wie?
Die ersten Strategien…
Was ist genau passiert?
2015 2016 2017
ASP.NET vNext ASP.NET 5
ASP.NET Core.NET Core
.NET Core 2.0.NET
Standard?
.NET Standard?
.NET Standard…
Sie mussten aber (technische) Schulden begleichen...
.NET CoreAPIs
.NET Framework APIs
SharedAPIs
Schuldenfrei
Wie wurden die Schulden beglichen?
Keine nativen GUIs wieWinForms oder WPF
ReflectionAPI umgebaut
Object.GetType() repräsentiertnicht mehr den vollen Reflection-Type
(«pay-for-play friendly»)
KeineApp Domains
«For code isolation we recommendprocesses and/or container»
Kein.NET RemotingMicrosoft emfpiehlt:
«a low-overhead plain textprotocol such as HTTP»
Keine Binary Serialisierung«[..] serialization should be a protocol
implemented on top of the available publicAPIs […] binary serialization requires intimate Knowledge […] which includes private state»
Kein SandboxingMicrosoft emfpiehlt:
«run under a user accountwith restricted permissions»
KeinSystem.Drawing
Kein:System.DataSystem.DirectoryServicesSystem.TransactionsSystem.Xml.XslSystem.Xml.SchemaSystem.Net.MailSystem.IO.PortsSystem.Workflow… Erscheinen teilweise in .NET Core 2.0
Wieso verzichtet man auf all das?
High-Performance und skalierbare Systeme
Docker Support
CLI Support (z.B. CI mit Bamboo, andere Entwicklungsumgebung)
Weil…
Und…
Cross-plattform (Tiefe Kosten im Betrieb)
Microservices Architektur
Open-Source (Tiefe Kosten in der Entwicklung)
Beweggründe der Bedag Informatik AG
Single-Page-ApplicationStrategie
Containerisierung Open-Source Strategie Betriebskosten senken
.NET Core recapC#Es kann wie gewohnt in C#, VB oder F# geschrieben werden
1
ModularLeichtgewichtig, da nur die nötigen Packages genutzt werden
2
Open-SourceDie Runtime, Libraries, Compiler, Sprachen und Tools sind alle unter GitHub publiziert
3
Cross-PlattformEinmal schreiben, überall nutzen4
Häufigere Deployments Den Puls der Weiterentwicklung
spüren, kurze Update-Zyklen8
CLIUnabhängigkeit vom Visual
Studio mit dem Command Line Interface
7
SchnellBenchmarkresultat: 8x schneller
als Node.js6
NuGetUpdates nur noch über NuGet, keine Windows Updates mehr,
dadurch entfallen die Major .NET Releases
5
.NETCore 7
6
5
4
82
3
1
C#8
DIE ERSTEN PROJEKTE
Projekte
PsyReg Online-Anmeldung
Anmeldung zur eidgenössischen
Prüfung für Gesundheitsberufe
Psychologieberuferegister
Kontext
Schichtenmodell
Screenshots
Security-Architekturcmp Security-Architektur
MedReg/Meduse Datenbank
Identity Store
MedReg Personenmodul / Meduse Web App
MedReg Web Services
Web Service Clients (MedReg)
Clients mit Webbrowser
Bedag IAM Bridge
PsyReg Web Frontend (AngularJS)
PsyReg REST Backend (.NET)
User Federation Provider
HTTPS (REST)
HTTPS (Bezug Ressourcen;Anwendung wird im Browserausgeführt)
HTTPS (Login Page)
OpenID Connect
OpenID Connect
HTTPS HTTPS
• OAuth2 und OpenID Connect• KeyCloak fungiert als Bridge• User werden in einem fremden
Identity Store gehalten• Authentifizierung Sache von
KeyCloak (Bedag IAM Bridge)• Autorisierung Sache der
Anwendung (PsyReg)
Security in .NET Core
Umgesetzt mit Middleware
Am Anfang…
• Keine Möglichkeit, OIDC out-of-the-box zu nutzen• Middleware geschrieben:
• OpenIdConnectAuthenticator• IUserInfoReader, OpenIdConnectUserInfoReaderKeycloakOpenIdConnectUserInfoReader
• Autorisierung über Attribut im Controller:
Heute möglich…
Dependency Injection
Aber: Sehr «Basic», keine attraktiven Registrierungsmöglichkeiten:
• Built-In in ASP.NET Core
Transient„[…] Created each time they are requested. [...]“
Scoped„[…] Created once per request. [...]“
Singleton„[…] Singleton lifetime services are created thefirst time they are requested [...]“
Unsere Anforderungen…
• CQRS Pattern mit ICommandHandler und IQueryHandler• Generische Registrierung aller davon implementierenden
Klassen• Eine gute Lösung musste her
Unsere Lösung:
GenericsContainer.Register(typeof(ICommandHandler<>), Assembly.Load(someAssembly));
Container.RegisterConditional(typeof(IValidator<>), typeof(LeftValidator<>), c => c.ServiceType.GetGenericArguments().Single().Namespace.Contains("Left"));Container.RegisterConditional(typeof(IValidator<>), typeof(RightValidator<>), c => c.ServiceType.GetGenericArguments().Single().Namespace.Contains("Right"));
services.AddSingleton<IControllerActivator>(new SimpleInjectorControllerActivator(Container));services.UseSimpleInjectorAspNetRequestScoping(Container);
Aktivierung sehr simpel:
Open-Source
Schnell
Testing
• NUnit wurde durch xUnit als neuer Standard in der Bedag abgelöst
• xUnit gehört zur• Wird von Microsoft eingesetzt• «Echtes» Mocking erst seit kurzem möglich
• Wegen Reflection Umbau• Zuerst mehrheitlich Integrationstests
• Nun auch Unittests mit «Moq»mailClientMock.Verify(m => m.SendMail(It.IsAny<MailContentDto>()), Times.Exactly(2));
Probleme bei den Tests
• Integrationstests aufgebaut • Kestrel In-Memory Webserver • Entity Framework Core In-Memory DB
• Aber: DB prüfte die referentielle Integrität nicht
Unsere Lösung:
• Alternative SQLite In-Memory
• Entity Framework Core hat einen SQLite Provider• Referentielle Integrität?
• Wird überprüft
var connection = new SqliteConnection("DataSource=:memory:");
Entity Framework Core
DB-First ReloadEntity
Local(Cache)
LazyLoading
DataAnnotationsValidierungen
Direkte Many-to-Many
Verbindung
Was fehlt?
Sehr schnell
Sehr (zu) schlank
Entity Framework Core kann aber…
… problemlos produktiv eingesetzt werden:• Keine Killerfeatures die fehlen• DB-First funktioniert auch mit bestehenden Tabellen
• Teilweise umständlich:
• Attribute vs. «ModelBuilder» teilweise doppelspurig und verwirrend
ASP.NET Core mit Kestrel hervorragend
Services
• Bereitstellung von SOAP out-of-the-box nicht mehr möglich
• WCF nur als Client• Verwendet für die Anbindung von
MS SQL Server Reporting Services
Mailversand
• Open-Source..• https://github.com/jstedfast/MailKit
• Sehr einfache Verwendung
System.Net.Mail fehlt, was tun?
Continuous Integration
Auftrag: CI mit und
• Bitbucket basiert auf GIT• Integration nahtlos im Visual Studio
• Ausser «Pull Requests», die funktionieren nur mit GitHub
auf deployen
Bamboo
• Agent für den Buildprozess• .NET Core SDK (für die CLI) muss auf dem Agent installiert
werden
• Danach folgt Script um Script um Script…1. «dotnet restore» (jedes einzelne Projekt…)2. «dotnet build */**/project.json» (Alle Projekte auf einmal)3. «dotnet test» (jedes Testprojekt einzeln…)4. «dotnet publish»5. «msdeploy.exe …..»
msdeploy.exe• Muss auf dem Agent installiert werden (benötigt Windows)
• Verträgt sich mit ASP.NET Core nicht so richtig• -enableRule:AppOffline funktioniert nicht (Case sensitivity Problem wegen
app_offline.htm)• https://github.com/aspnet/AspNetCoreModule/issues/50
• Unsere Lösung:
• StopAppPool
• Deploy
• StartAppPool
msdeploy -verb:sync -source:recycleApp -dest:recycleApp="abc",computerName="https://abc/msdeploy.axd?site=abc",recycleMode="StopAppPool",authType="basic",userName="*",password="*" -allowUntrusted=true
msdeploy -verb:sync -source:contentpath="C:\temp\abc" -dest:contentPath="abc",computerName=https://abc/msdeploy.axd?site=abc,username="*",password="*",authType=basic -allowUntrusted=true
msdeploy -verb:sync -source:recycleApp -dest:recycleApp="abc",computerName="https://abc/msdeploy.axd?site=abc",recycleMode="StartAppPool",authType="basic",userName="*",password="*" -allowUntrusted=true
LESSONS LEARNED
IIS und ASP.NET Core sind nicht die besten Freunde
• «Kestrel» ist eigener Webserver, IIS nur «Durchlauferhitzer» (Reverse Proxy)
Falls etwas nicht funktioniert, aktualisiere alles…
• Wenn du komische Fehler erhältst, aktualisiere alle deine NuGet-Packages…
Stell dich auf wöchentliche Updates ein…
• Beispiel Entity Framework Core (RC1, RC2, 1, 1.1, 1.1.1)
• Und es werden wirklich Fehler korrigiert…“The column prefix 't0' does not match with a table name or alias name used in the query. No column name was specified for column 1 of 't1’” mit Entity Framework Core 1.1
Mit Entity Framework Core 1.1.1 hats danach tatsächlich keinen Fehler mehr gegeben…
Halte Ausschau nach Alternativen
GDI+ oder System.Net.Mailfehlen dir?
Es gibt (fast) immer eine Alternative…
Die reelle Gefahr für einen Wildwuchs an Frameworks besteht jedoch
Verlass dich auf nichts…
Du hast dich voll auf project.json eingestellt und findest es genial?-> Microsoft wechselt es aus mit dem XML-basierten File *.csproj
Du hast eine tolle Middleware geschrieben?-> Nach einem Update (z.B. auf 1.1.1) funktioniert sie eventuell nicht mehr..
Entscheide, welche Plattform du unterstützen willst…
• Gebt die richtige «RIDs» bei den Runtimes an:
Damit «dotnet publish» auch das richtige tut..
CI ist (aktuell) hart, aber…
• Microsoft lässt «project.json» fallen• «csproj» für «msbuild» wird (wieder) eingeführt• Stufenabhängige Konfiguration für appsettings wieder
möglich• Entwicklung, Test, Produktion etc.
https://docs.microsoft.com/en-us/dotnet/core/tools/cli-msbuild-architecture
Anordnung der Middleware ist enorm wichtig
(ASP).NET Core ist einfach besser…
Gegenüber ASP.NET MVC oder WebForms professioneller, wartbarer und macht einfach mehr Spass:
DependencyInjection
Identity Kestrel ServicesLogging Options Startup
Middleware Testing Entity Framework
Core
und mehr
…
TIPPS & TRICKS
Hosting von ASP.NET Core auf dem IIS
• Installation vom «.NET Core Windows Server Hosting bundle»• Neustart IIS
• net stop was /y • net start w3svc
• Im Program.cs muss UseIISIntegration() ausgeführt werden:var host = new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseIISIntegration().UseStartup<Startup>().Build();
Code:
Hosting von ASP.NET Core auf dem IIS
Konfiguration:
Deployment: (dotnet publish)
ASP.NET Core auf dem IIS
• Im IIS Manager eine neue Website erstellen• Application Pool -> «No Managed Code»
• Umgebungsvariable «dotnet» muss gesetzt sein• Sonst:
IIS:
Zu beachten
Entity Framework DbSet-Naming-Problematik
• “EF Core: Table names now taken from DbSet names (starting in RC2)” https://github.com/aspnet/Announcements/issues/167
public DbSet<User> Users Tabellenname wird als «Users» erwartet
Umgehung:protected override void OnModelCreating(ModelBuilder modelBuilder){
foreach (var entity in modelBuilder.Model.GetEntityTypes()){
entity.Relational().TableName = entity.DisplayName();}
}
Entity Framework Core mit PostgreSQL
• Nuget «Npgsql»• Code kann belassen werden, wichtig aber:
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.HasPostgresExtension("uuid-ossp");}
var optionsBuilder = new DbContextOptionsBuilder();optionsBuilder.UseNpgsql("ConnectionString");
Bei der Registrierung von DI (Startup):
DbContext:
Generische Registrierung
• Wie komme ich zu den Assemblies nur meiner Projekte?Container.Register(typeof(ICommandHandler<>), Assembly.Load(assemblies?));
var libraries = from lib in DependencyContext.Default.RuntimeLibrarieswhere lib.Type == "project"select lib;
var assemblies = libraries.Select(name => Assembly.Load(new AssemblyName(name.Name)));
Macht auch Integrationstests
• In-Memory Webserver• In-Memory DB• Akzeptanzkriterien testen• Beispiel auf meinem GitHub
• syh-42
HERZLICHEN DANK FÜR DIE AUFMERKSAMKEITDIE FRAGERUNDE IST NUN ERÖFFNET…