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
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
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<IMyComponent, MyComponent>(); // Alternative registrations serviceCollection.AddSingleton<IMyComponent>(provider => new MyComponent()); serviceCollection.AddScoped<IMyComponent, MyComponent>(); serviceCollection.AddTransient<IMyComponent, MyComponent>(); // Creates DI container IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); // Resolve types IMyComponent myComponent = serviceProvider.GetRequiredService<IMyComponent>(); 1
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<MyConfiguration>(config.GetSection("my:configuration")); // Optional serviceCollection.AddSingleton(provider => provider.GetRequiredService<IOptions<MyConfiguration>>().Value); public class MyConfiguration { public int Value { get; set; } } public class MyComponent { public MyComponent(IOptions<MyConfiguration> myConfigWrapper /* MyConfiguration myConfig*/) { } } 3 { "my": { "config": { "value": 42 } } }
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");
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<MyConfiguration>("My.Config") .As<IMyConfiguration>(); { "my": { "config": { "value": 42 } } } public interface IMyConfiguration { int Value { get; } } public class MyConfiguration : IMyConfiguration { public int Value { get; set; } public MyConfiguration(IMyService service) { ... } }
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
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
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();
• Optional: Startup can implement the interface IStartup ASP.NET Core var host = new WebHostBuilder() ... .UseStartup<Startup>() .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
☺ • 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"); }); }
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<MyConfiguration>(); ... 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<Startup>()); }) .UseStartup<Startup>() .Build(); host.Run(); } 7
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()); ... } }
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
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(); } } <!DOCTYPE html> <html> <head> <title>MVC</title> </head> <body> Date: @DateTime.Now </body> </html>
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<IHttpRequestIdentifierFeature>(new MyHttpRequestIdentifierFeature()); await _next(ctx); } } public class MyHttpRequestIdentifierFeature : IHttpRequestIdentifierFeature { public string TraceIdentifier { get; set; } = Guid.NewGuid().ToString(); } 9
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<LogEnricherMiddleware>(); 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); } } }
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<IActionResult> Do() { CancellationToken cancellationToken = _appLifetime.ApplicationStopping; await Task.Delay(5000, cancellationToken); return Json(DateTime.Now); } }
manually Entity Framework Core public class DemoDbContext : DbContext { public DbSet<DemoRecord> Records { get; set; } public DemoDatabaseContext(DbContextOptions<DemoDbContext> options) :base(options) { } } public class DemoRecord { [Key] public Guid Id { get; set; } [Required, MaxLength(100)] public string Name { get; set; } }
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 <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1"> <PrivateAssets>All</PrivateAssets> </PackageReference> <!--If you prefer CLI instead of Package Manager Console--> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> </ItemGroup> > dotnet ef migrations add Initial_Migration > dotnet ef database update
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<DemoDbContext> { public DemoDbContext Create(DbContextFactoryOptions options) { var builder = new DbContextOptionsBuilder<DemoDbContext>(); builder.UseSqlServer("server=(local);database=Demo;integrated security=true;"); return new DemoDbContext(builder.Options); } } 10