Slide 1

Slide 1 text

Pawel Gerr @pawelgerr Web-based Applications with .NET Core

Slide 2

Slide 2 text

• .NET Standard • .NET Core • Dependency Injection • Configuration • Logging • ASP.NET Core • Kestrel • Request Pipeline • Startup / Shutdown • Entity Framework Core • Code-first / Database-first • Migrations Agenda

Slide 3

Slide 3 text

.NET Standard

Slide 4

Slide 4 text

vs .NET Core • Specification of .NET APIs • .NET Core is a cross-platform runtime: Windows, Linux, MacOS • Versions: 1.0 – 1.6 (v2.0 in Q3 2017) • Runtime support (list is not complete) • .NET Core 1.0 ➡ 1.0 – 1.6 • .NET Core 2.0 ➡ 1.0 – 2.0 • .NET 4.5 ➡ 1.0 – 1.1 • .NET 4.6 ➡ 1.0 – 1.3 • .NET 4.6.1 ➡ 1.0 – 2.0 • Open Source: https://github.com/dotnet/core .NET Standard 2.0 1.6 1.2 1.0

Slide 5

Slide 5 text

vs Portable Class Library (PCL) PCL (platform specific) .NET Standard (platform agnostic) .NET Standard 2.0 1.6 1.2 1.0 Platform A Platform B Platform C

Slide 6

Slide 6 text

Lower version for libraries Bigger version ➡ bigger capabilities ➡ worse platform support Lower version ➡ less capabilities ➡ better platform support .NET Standard

Slide 7

Slide 7 text

Multi-Target Libraries • Provide different APIs via conditional compilation • System.Runtime supports • .NETFramework 4.5, 4.6.2 • .NETStandard 1.0, 1.2, 1.3, 1.5 • .... • Can have different dependencies • System.Runtime dependencies • .NETFramework 4.5, 4.6.2 ➡ No dependencies • .NETStandard 1.0 ➡ Microsoft.NETCore.Platforms, ... .NET Standard IntelliSense for type TypeCode #if NETSTANDARD1_3 || NETSTANDARD1_4 var code = TypeCode.Boolean; #endif

Slide 8

Slide 8 text

Platform-dependent code • Use RuntimeInformation to determine the platform .NET Standard if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { ... } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { ... } else { ... }

Slide 9

Slide 9 text

Tooling changes • Requires Visual Studio 2017, Visual Studio Code or JetBrains Rider • .NET Core is very modular ➡ everthing is a nuget package • Implicit references on meta packages: NetStandard.Library and Microsoft.NETCore.App • Standard files (like .cs) are included into project per default • Nuget v4: • Project/Nuget package references as well as nuspec metadata are in *.csproj • Transitive package restore • Assembly info can be defined in *.csproj as well instead of in AssemblyInfo.cs • CLI: dotnet restore/build/pack/publish .NET Standard

Slide 10

Slide 10 text

Drawbacks • Visual Studio 2017 is slower in comparison when working with full-blown .NET Framework like .NET 4.6 • ReSharper slows Visual Studio even more • Manual adjustments of project files (*.csproj) are required for some features (e.g. multi-targeting) • Application consisting of 300+ assemblies is easy to reach • Nuget (v3) can be very slow when using „old“ tooling with .NET Standard compatible packages • A few nuget packages „forget“ to copy necessery assemblies to output directory on build • Search for package containing specific type (http://packagesearch.azurewebsites.net/, https://apisof.net/) • Some .NET members (properties, methods) throw PlatformNotSupportedException .NET Standard

Slide 11

Slide 11 text

.NET Core

Slide 12

Slide 12 text

Dependency Injection (DI) • IServiceCollection will be provided by ASP.NET Core • Otherwise create with new ServiceCollection() • Recommendation: use your favorite DI framework Issue when working with objects implementing IDisposable: http://weblogs.thinktecture.com/pawel/2017/02/aspnet-core-dependecy-injection-disposing.html .NET Core serviceCollection.AddSingleton(); // Alternative registrations serviceCollection.AddSingleton(provider => new MyComponent()); serviceCollection.AddScoped(); serviceCollection.AddTransient(); // Creates DI container IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); // Resolve types IMyComponent myComponent = serviceProvider.GetRequiredService(); 1

Slide 13

Slide 13 text

Configuration • Key-value pair based configuration • Usage .NET Core IConfiguration config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary() { {"Key", "Value"} }) // NuGet package: Microsoft.Extensions.Configuration.EnvironmentVariables .AddEnvironmentVariables() // NuGet package: Microsoft.Extensions.Configuration.CommandLine .AddCommandLine(args) // NuGet package: Microsoft.Extensions.Configuration.Json .AddJsonFile("configuration.json", optional:true, reloadOnChange:true) ... .Build(); string value = config["Key"]; // string value = config.GetSection("Key").Value; 2

Slide 14

Slide 14 text

Configuration – Strongly Typed • Strongly typed way of accessing settings • MyConfiguration will be injected by the DI .NET Core IServiceCollection serviceCollection; IConfiguration config; // Registers necessary types like IOptions<> serviceCollection.AddOptions(); // Registers „MyConfiguration“ serviceCollection.Configure(config.GetSection("my:configuration")); // Optional serviceCollection.AddSingleton(provider => provider.GetRequiredService>().Value); public class MyConfiguration { public int Value { get; set; } } public class MyComponent { public MyComponent(IOptions myConfigWrapper /* MyConfiguration myConfig*/) { } } 3 { "my": { "config": { "value": 42 } } }

Slide 15

Slide 15 text

Configuration – User Secrets • For sensitive data like API-keys, passwords, connection strings, etc. during development • Internal functionality: • Loads the content of a JSON-file from user profile • The file path is derived from the provided User-Secret-Id "MySecretId" https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets .NET Core var configBuilder = new ConfigurationBuilder(); ... configBuilder.AddUserSecrets("MySecretId");

Slide 16

Slide 16 text

Configuration – Alternative • Thinktecture.Configuration (https://github.com/PawelGerr/Thinktecture.Configuration) • Strongly Typed • Support for abstract types (e.g. for inner properties) • DI Support (non-default constructor) • Supports „overrides“ • ... .NET Core var builder = new Autofac.ContainerBuilder(); ... builder.RegisterJsonFileConfigurationProvider("./configuration.json"); builder.RegisterJsonFileConfiguration("My.Config") .As(); { "my": { "config": { "value": 42 } } } public interface IMyConfiguration { int Value { get; } } public class MyConfiguration : IMyConfiguration { public int Value { get; set; } public MyConfiguration(IMyService service) { ... } }

Slide 17

Slide 17 text

Logging • Logging library from Microsoft • Output .NET Core ILoggerFactory loggerFactory = new LoggerFactory() // NuGet package: Microsoft.Extensions.Logging.Console .AddConsole(LogLevel.Trace, includeScopes: true); ILogger logger = loggerFactory.CreateLogger("MyLogger"); using (logger.BeginScope("My scope")) { logger.LogInformation(eventId: 42, message: "My log message"); } info: MyLogger[42] => My scope My log message 4

Slide 18

Slide 18 text

Logging with Serilog • Use extension method AddSerilog • Console Output .NET Core [16:51:32 INF] [MyLogger - { Id: 42 }] ["My scope"] => My log message Logger serilogLogger = new LoggerConfiguration() .WriteTo.LiterateConsole(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext} - {EventId}] {Scope} => {Message}{NewLine}{Exception}") .CreateLogger(); ILoggerFactory loggerFactory = new LoggerFactory() .AddSerilog(serilogLogger); ILogger logger = loggerFactory.CreateLogger("MyLogger"); using (logger.BeginScope("My scope")) { logger.LogInformation(eventId: 42, message: "My log message"); }

Slide 19

Slide 19 text

ASP.NET Core

Slide 20

Slide 20 text

Parts • Web servers • Kestrel: cross-platform • WebListener: Windows only • No separation between MVC and Web API • MVC and Web API can be self-hosted now • Build-in dependency injection, configuration and logging infrastructure • Missing pieces • SignalR (in development) • Web Forms (no plans to port it to .NET Core) ASP.NET Core

Slide 21

Slide 21 text

Web server Kestrel • Cross-platform • HTTPS support • Not as feature rich as e.g. IIS but ... • IIS/Nginx/Apache can be put in front of Kestrel (recommended when using on the internet) Setup Issues: http://weblogs.thinktecture.com/pawel/2017/02/aspnet-core-with-iis-setup-issues.html ASP.NET Core HTTPS IIS as reverse proxy Kestrel Internet HTTP

Slide 22

Slide 22 text

Kestrel Setup • Minimalistic setup • Console Output ASP.NET Core static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .Configure(app => { app.Run(async context => await context.Response.WriteAsync("Demo")); }) .Build(); host.Run(); } Hosting environment: Production Content root path: C:\Sources\KestrelSetup\bin\Debug\netcoreapp1.1 Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down. 5

Slide 23

Slide 23 text

Environments • By convention: Development, Staging, Production (default) • Can be set via code or environment variable • via command line • via code • Determine the environment by injecting IHostingEnvironment ASP.NET Core set ASPNETCORE_ENVIRONMENT=Development var host = new WebHostBuilder() .UseKestrel() .UseEnvironment("Development") ... // IHostingEnvironment env if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

Slide 24

Slide 24 text

Startup class • Configuration of the ASP.NET pipeline in Startup • Optional: Startup can implement the interface IStartup ASP.NET Core var host = new WebHostBuilder() ... .UseStartup() .Build(); public class Startup { public void ConfigureServices(IServiceCollection services) { // configuration of the DI } public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Demo"); }); } } 6

Slide 25

Slide 25 text

DI with 3rd party library (Autofac) – The easy way ☺ • Register Autofac with WebHostBuilder • The method ConfigureContainer will be called right after ConfigureServices ASP.NET Core var host = new WebHostBuilder() .ConfigureServices(services => services.AddAutofac()) ... public void ConfigureServices(IServiceCollection services) { // DI configuration } public void ConfigureContainer(ContainerBuilder builder) { // DI configuration } public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Demo"); }); }

Slide 26

Slide 26 text

• Web Application = Web API/MVC + More • More control over startup and shutdown of the web server • Multi-step setup: logging ➡ configuration ➡ misc preparations ➡ web server • Graceful shutdown: shutdown of endpoints ➡ processing of outstanding data • Execution of (periodic) tasks DI - More control over the application life cycle ASP.NET Core ContainerBuilder containerBuilder = GetContainerBuilder(); using (var container = containerBuilder.Build()) { var myConfig = container.Resolve(); ... using (var scope = container.BeginLifetimeScope( builder => AdditionalDeps(builder, myConfig))) { ... StartWebServer(scope); ... } } private void StartWebServer(ILifetimeScope scope) { var host = new WebHostBuilder().UseKestrel() .ConfigureServices(s => { s.AddTransient(provider => scope.Resolve()); }) .UseStartup() .Build(); host.Run(); } 7

Slide 27

Slide 27 text

DI - More control over the application (Startup) • Most ASP.NET libraries offer extension methods for IServiceCollection only • The web server uses a child scope for resolution of dependencies (like controllers) • Requires PR: https://github.com/autofac/ Autofac.Extensions.DependencyInjection/ pull/14 or fork: https://github.com/PawelGerr/ Autofac.Extensions.DependencyInjection or the ASP.NET components must be registered with the root lifetime scope (i.e. the IContainer itself) ASP.NET Core public class Startup { private const string _WEBSERVER_SCOPE_NAME = "ASPNET_WEBSERVER_ROOT"; public Startup(ILifetimeScope scope) { _scope = scope; } public IServiceProvider ConfigureServices(IServiceCollection services) { // register ASP.NET Core components _aspnetScope = _scope.BeginLifetimeScope(_WEBSERVER_SCOPE_NAME, builder => builder.Populate(services, _WEBSERVER_SCOPE_NAME)); return new AutofacServiceProvider(_aspnetScope); } public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime) { appLifetime.ApplicationStopped.Register(() => _aspnetScope?.Dispose()); ... } }

Slide 28

Slide 28 text

• AddMvc adds MVC (incl. Razor Engine) and Web API components to DI • UseMvc adds MVC and Web API into request pipeline Performance tips: http://weblogs.thinktecture.com/pawel/2017/03/aspnet-core-webapi-performance.html Adding MVC and Web API ASP.NET Core public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); ... } public void Configure(IApplicationBuilder app) { app.UseMvc(); } } [Route("[controller]")] public class DemoController : Controller { [HttpGet("GetTime")] public IActionResult GetTime() { return Json(DateTime.Now); } } http://localhost:5000/demo/gettime 8

Slide 29

Slide 29 text

MVC • Razor support • Dependency injection in views • Use this feature with caution • Complex business logic and database access does not belong into a view ASP.NET Core [Route("[controller]")] public class DemoController : Controller { [HttpGet("Index")] public IActionResult Index() { return View(); } } MVC Date: @DateTime.Now

Slide 30

Slide 30 text

Request Pipeline • Configure pipeline by adding middleware components ASP.NET Core public void Configure(IApplicationBuilder app) { app.UseMiddleware(); app.UseStaticFiles(); app.UseMvc(); } MyMiddleware StaticFiles Middleware MVC Middleware Request Response Client Request Pipeline

Slide 31

Slide 31 text

Logs Correlation – Request Id • ASP.NET Core provides a RequestId • Can be get and set via HttpContext.TraceIdentifier • Alternative: change the ASP.NET Core feature responsible for generation of the id ASP.NET Core var serilogLoggerConfig = new LoggerConfiguration() .WriteTo.LiterateConsole(outputTemplate: "... {RequestId} ...") ...; public class MyHttpRequestIdentifierFeatureMiddleware { public MyHttpRequestIdentifierFeatureMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext ctx) { ctx.Features.Set(new MyHttpRequestIdentifierFeature()); await _next(ctx); } } public class MyHttpRequestIdentifierFeature : IHttpRequestIdentifierFeature { public string TraceIdentifier { get; set; } = Guid.NewGuid().ToString(); } 9

Slide 32

Slide 32 text

Logs Correlation – Across Process Boundaries • Serilog log enricher for more advanced use cases • Add a middleware before MVC • Push an existing or new id to LogContext • Send the id via HTTP headers for correlation across process boundaries ASP.NET Core var serilogLoggerConfig = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.LiterateConsole("... {CorrelationId} ...") ...; public void Configure(IApplicationBuilder app) { app.UseMiddleware(); app.UseMvc(); } public class LogEnricherMiddleware { ... public async Task Invoke(HttpContext ctx) { string id; if ((id = ctx.Request.Headers["CorrelationId"].FirstOrDefault()) == null) id = Guid.NewGuid().ToString(); ctx.Response.Headers["CorrelationId"] = id; using (LogContext.PushProperty("CorrelationId", id)) { await _next(ctx); } } }

Slide 33

Slide 33 text

Graceful Shutdown • Controller actions are not cancelled on server shutdown • Use IApplicationLifetime to get a CancellationToken • Dosn‘t fire when client cancels the request • ASP.NET Core v2.0: use HttpContext.RequestAborted to get notified when client is disconnected ASP.NET Core [Route("[controller]")] public class DemoController : Controller { public DemoController(IApplicationLifetime appLifetime) { _appLifetime = appLifetime; } [HttpGet("Do")] public async Task Do() { CancellationToken cancellationToken = _appLifetime.ApplicationStopping; await Task.Delay(5000, cancellationToken); return Json(DateTime.Now); } }

Slide 34

Slide 34 text

Entity Framework Core

Slide 35

Slide 35 text

aka Entity Framework 7 • Cross-platform O/R-Mapper made by Microsoft • Supports code-first and database-first • Entity Framework Migrations for updating database schema • Supported database (list is not complete) • MS SQL Server • Postgres • Sqlite • In-Memory (for testing purposes) Entity Framework Core

Slide 36

Slide 36 text

Code-First • The database context and entity classes are created manually Entity Framework Core public class DemoDbContext : DbContext { public DbSet Records { get; set; } public DemoDatabaseContext(DbContextOptions options) :base(options) { } } public class DemoRecord { [Key] public Guid Id { get; set; } [Required, MaxLength(100)] public string Name { get; set; } }

Slide 37

Slide 37 text

Database-First • Add following to the *.csproj • Scaffolding via CLI Entity Framework Core All All > dotnet ef dbcontext scaffold "Server=(local);Database=Demo;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer

Slide 38

Slide 38 text

Migrations • Add to *.csproj with the database context • EF Migration requires a parameterless constructor or an implementation of IDbContextFactory • Creates new migration • Update database via CLI, Package Manager Console or via code Entity Framework Core All > dotnet ef migrations add Initial_Migration > dotnet ef database update

Slide 39

Slide 39 text

Migrations - IDbContextFactory • IDbContextFactory has to be in the same project as the dabase context • The factory can be internal Assembly Versioning Mismatch: http://weblogs.thinktecture.com/pawel/2017/03/entity-framework-core- migrations-assembly-version-mismatch.html Entity Framework Core internal class EfMigrationDbContextFactory : IDbContextFactory { public DemoDbContext Create(DbContextFactoryOptions options) { var builder = new DbContextOptionsBuilder(); builder.UseSqlServer("server=(local);database=Demo;integrated security=true;"); return new DemoDbContext(builder.Options); } } 10

Slide 40

Slide 40 text

Dependency Injection • Registers with DI with lifetime „scoped“ • Alternative registration with DI Entity Framework Core IServiceCollection services; services.AddDbContext(optionsBuilder => optionsBuilder.UseSqlServer("ConnString")); ContainerBuilder builder; builder.RegisterType().AsSelf().InstancePerLifetimeScope(); builder.Register>(context => { return new DbContextOptionsBuilder() .UseSqlServer("ConnString") .Options; }) .AsSelf() .SingleInstance();

Slide 41

Slide 41 text

Demo • https://github.com/PawelGerr/Presentation-Web- based-Applications-with-.NET-Core Slides • https://speakerdeck.com/pawelgerr Resources .NET Core / .NET Standard • https://www.microsoft.com/net/core/platform • https://github.com/dotnet/core • https://docs.microsoft.com/en- us/dotnet/articles/standard/library ASP.NET Core • https://docs.microsoft.com/en-us/aspnet/core/ ASP.NET Core • https://docs.microsoft.com/en-us/ef/core/