Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web-based Applications with .NET Core

Web-based Applications with .NET Core

Pawel Gerr

June 29, 2017
Tweet

More Decks by Pawel Gerr

Other Decks in Programming

Transcript

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

    View full-size slide

  2. • .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

    View full-size slide

  3. .NET Standard

    View full-size slide

  4. 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

    View full-size slide

  5. 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

    View full-size slide

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

    View full-size slide

  7. 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

    View full-size slide

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

    View full-size slide

  9. 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

    View full-size slide

  10. 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

    View full-size slide

  11. 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

    View full-size slide

  12. 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

    View full-size slide

  13. 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 }
    }
    }

    View full-size slide

  14. 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");

    View full-size slide

  15. 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) { ... }
    }

    View full-size slide

  16. 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

    View full-size slide

  17. 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");
    }

    View full-size slide

  18. ASP.NET Core

    View full-size slide

  19. 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

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

  22. 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();

    View full-size slide

  23. 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

    View full-size slide

  24. 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"); });
    }

    View full-size slide

  25. • 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

    View full-size slide

  26. 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());
    ...
    }
    }

    View full-size slide

  27. • 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

    View full-size slide

  28. 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


    View full-size slide

  29. 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

    View full-size slide

  30. 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

    View full-size slide

  31. 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);
    }
    }
    }

    View full-size slide

  32. 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);
    }
    }

    View full-size slide

  33. Entity Framework Core

    View full-size slide

  34. 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

    View full-size slide

  35. 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; }
    }

    View full-size slide

  36. 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

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide

  39. 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();

    View full-size slide

  40. 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/

    View full-size slide