$30 off During Our Annual Pro Sale. View Details »

Einmal API mit allem bitte - ASP.NET Core MVC und SignalR in Action

Einmal API mit allem bitte - ASP.NET Core MVC und SignalR in Action

Moderne APIs können so viel mehr als nur gängige HTTPS-Endpunkte bereitzustellen: Zum einen die Möglichkeit zur (optionalen) Echtzeitkommunikation auf Basis von WebSockets. Mit Hilfe von Microsofts Open-Source-Plattform ASP.NET Core MVC und SignalR lassen sich im Handumdrehen Web-APIs und Push Services entwickeln. Doch damit nicht genug: Weitere Themen wie Security, Rate Limiting, eine sinnvolle Dokumentation und ein vernünftiges, zentralisiertes Logging sind mit von der Partie. Und jeder dieser einzelnen Bereiche bietet uns eine neue Herausforderung.

Sebastian Gingter und Manuel Rauber zeigen Ihnen in diesem Workshop, wie man mit .NET Core moderne APIs auf und für verschiedene Plattformen entwickeln kann. Zusätzlich wird durch den Einsatz von Entity Framework Core die Anbindung an unterschiedliche Datenbanken ermöglicht. Durch den Einsatz weiterer Open-Source-Bibliotheken für das Logging oder zur automatischen Generierung einer Swagger/OpenAPI-Dokumentation ergänzen wir das API um weitere Funktionalitäten. Eine Clientanwendung, das Deployment auf unterschiedliche Plattformen und das Deployment in die Cloud runden den Workshop ab – vollständig End-to-End und einmal mit allem eben!

GitHub: https://github.com/thinktecture/api-ms-summit-spring-2019-services-dotnetcore

Manuel Rauber

June 19, 2019

More Decks by Manuel Rauber

Other Decks in Programming


  1. ASP.NET Core MVC und SignalR in Action! Einmal API mit

    allem, bitte. Sebastian Gingter Manuel Rauber @PhoenixHawk @ManuelRauber Consultant Consultant
  2. Speakers manuel.rauber@thinktecture.com @manuelrauber https://manuel-rauber.com Microsoft MVP sebastian.gingter@thinktecture.com @PhoenixHawk https://gingter.org/

  3. Time Doing 09:00 – 10:30 Part I 10:30 – 11:00

    ☕ 11:00 – 12:30 Part II 12:30 – 13:30 13:30 – 15:00 Part III 15:00 – 15:30 ☕ & 15:30 – 17:00 Part IV Timetable
  4. • Lightweight service-based architecture • Functional services with dedicated interfaces

    • Use other services, like database or file system • (JSON-based) HTTPS Web APIs • Application push services via WebSocket • SignalR • Socket.io Target Architecture HTTP HTTPS WebSocket Service A Service B Service C Web APIs (ASP.NET, Node.js, …) HTML5-Application (Single-Page Application) Web Cordova Electron
  5. • Functional services with dedicated interfaces • Strict REST services

    do not model real world • Better: Design interfaces according to use case • Single Web API can use multiple backend services • (JSON-based) HTTPS Web APIs • Embraces usage of common denominator • Content Negotation • HTTP Status Codes • Resources are mapped to URLs • Lightweight & scalable Target Architecture HTTP HTTPS WebSocket Service A Service B Service C Web APIs (ASP.NET, Node.js, …) AuthN & AuthZ Client
  6. From specification to implementation .NET Standard & .NET Core

  7. What exactly is it? • .NET Standard is an open

    source specification: https://github.com/dotnet/standard • Represents a common set of API to be implemented by all .NET platforms • .NET Standard replaces PCLs .NET Standard
  8. One library to rule them all .NET tomorrow today! https://blogs.msdn.microsoft.com/dotnet/2016/09/26/introducing-net-standard/

  9. .NET Core 3.0 Future

  10. Overview • .NET Core is an open source implementation of

    the .NET Standard specification: • https://github.com/dotnet/core • Runs on Linux, macOS and Windows • CLI tools available • Supports C# and F# • Compatible to .NET Framework, Xamarin and Mono (due to .NET Standard) • Fully supported by Microsoft (LTS support three years after GA) .NET Core
  11. Platform-specific APIs Some APIs are not available everywhere (e.g. Registry,

    Windows Identity, Reflection Emit) and will throw a PlatformNotSupportedException. .NET Core
  12. IDEs .NET Core VS 2017/2019 VS Code JetBrains Rider VS

    for Mac
  13. Use case depending alternatives • Node.js • JavaScript, TypeScript, CoffeeScript,

    … • Restify, Express, Koa.js • TypeORM, SequelizeJS, ORM • Node Package Manager, YARN • Java • Jersey, RESTful WebServices • Hibernate, ORM • Maven, Package Management • Ruby, Python, … .NET Core
  14. Summary • Powerful language/tools and base for creating cross-platform .NET

    libs & apps • Good and growing API support • Targeting platform specific APIs could result in an error or conditional compilation • Cloud-ready • „No breaking changes between versions“ .NET Standard & .NET Core
  15. No … No … breaking … No … breaking …

    changes … No … breaking … changes … between … No … breaking … changes … between … versions …
  16. Ready for your next Todo App? ;-) The Sample

  17. Security Token Service Identity Server Database Todo- API Push- API

    Realtime-Data Users Web-Server SPA (static files) Demo Architecture
  18. Security Token Service Identity Server Database Todo- API Push- API

    Realtime-Data Users Web-Server SPA (static files) Demo Architecture
  19. Demo Client Application

  20. Cross-Platform Web Application & Web APIs ASP.NET Core

  21. Overview • Unifies MVC and Web APIs • Built-in dependency

    injection • Cloud-ready configuration system • HTTP Request Pipeline • Hostable in IIS or as Self Host • Everything’s a NuGet Package • Leads to having a lot packages installed (“Hello node_modules J”) • Built on .NET Core • Open Source, https://github.com/aspnet/Home • “Can I port my application to .NET Core?” • https://icanhasdot.net/ ASP.NET Core
  22. Compared to ASP.NET ASP.NET Core • Runs on Linux, macOS,

    Windows • C#, F# • MVC & Web API, Razor Pages • New platform, new toolings • Multiple versions per machine • MSDN: “Ultra Performance” ASP.NET • Runs on Windows • C#, F#, VB.NET • MVC, Web API, WebForms, WebPages, SignalR • Mature platform • One version per machine • MSDN: “High Performance” ASP.NET Core
  23. Hosting • Kestrel: ASP.NET Core’s Web Server • IISIntegration to

    host in IIS • Mandatory for App Services • Specifies a class used for Startup configuration ASP.NET Core public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); } }
  24. Startup • ConfigureServices: Used for configuring the built-in dependency injection

    • Configure: Used for configuring the HTTP pipeline via IApplicationBuilder ASP.NET Core public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { } }
  25. Dependency Injection • Built-in dependency injection • Constructor injection should

    be preferred over direct DI container access • Overriding the container is possible, e.g. to use AutoFac instead • Throws an error when a dependency could not be resolved (e.g. wrong container configuration) ASP.NET Core
  26. Dependency Injection • Supports three lifetimes • Transient: Creates an

    instance each it is requested • Scoped: Creates an instance once per request • Singleton: Creates an instance once per application ASP.NET Core public void ConfigureServices(IServiceCollection services) { services.AddTransient<IIdGenerator, IdGenerator>(); services.AddScoped<ICustomerService, DatabaseCustomerService>(); services.AddSingleton<ICacheService, InMemoryCacheService>(); }
  27. Pipeline • Customizable HTTP pipeline • Every request will run

    through • Order in code defines the order of the pipeline • Nesting of pipelines is possible • IApplicationBuilder is used to define the pipeline ASP.NET Core https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware public void Configure(IApplicationBuilder app) { app.UseExceptionHandler("/Home/Error"); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); } Middleware 1 // Server logic next() // more logic Middleware 2 // Server logic next() // more logic Middleware 3 // Server logic // more logic Request Response Client Logging Authentication …
  28. Multiple Environments • Support multiple environments like Development, Staging or

    Production out-of-box • Can be set via environment variable ASPNETCORE_ENVIRONMENT • Convention based application startup • Startup, StartupDevelopment, Startup{Environment} • Careful, using webhostBuilder.UseStartup<Startup>() overrides this behavior • Configure, ConfigureDevelopment, Configure{Environment} • ConfigureServices, ConfigureDevelopmentServices, Configure{Environment}Services ASP.NET Core
  29. Multiple Environments IHostingEnvironment for checking environment at runtime ASP.NET Core

    public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment() || env.IsProduction() || env.IsStaging() || env.IsEnvironment(”MyEnv")) { } }
  30. Configuration System • ConfigurationBuilder is used to define configuration sources

    • Order is used for configuration overriding, later defined sources override previous defined • Optional sources and sources depending on the environment are possible ASP.NET Core public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); }
  31. Options Pattern • Typed (nested) Configurations • Injectable • Runtime-reloadable

    ASP.NET Core public void ConfigureServices(IServiceCollection services) { services.Configure<PushServerSettings>(Configuration.GetSection("PushServer")); } public class PushService { private readonly PushServerSettings _config; public PushService(IOptions<PushServerSettings> settingsAccessor) { _config = settingsAccessor.Value; } }
  32. Authentication • Middleware within the pipeline • “Plug & Play”

    solutions for authentication • ASP.NET Identity • IdentityServer 4, open source project • Different authentication schemes • Cookies • Bearer • Basic • External providers • Custom ASP.NET Core
  33. Controllers • Two base classes for controllers • ControllerBase: Preferred

    for Web APIs, offers everything except Views • Controller: Preferred for MVC, inherits from ControllerBase and adds View capabilities • Offer various methods for returning a result • Ok() / NotFound() / StatusCodeResult() • FileResult() / FileContentResult() / FileStreamResult() • Offers access to authenticated user via User property • Can be asynchronous using async/await pattern • If enabled, automatic Content Negotiation ASP.NET Core
  34. Routing • Routing: Mapping URLs to actions (methods within a

    controller) • Routing via definition in pipeline • Routing via attributes • Routing via custom routing handler • Support for all important HTTP verbs like GET, POST, PUT, DELETE ASP.NET Core public void Configure(IApplicationBuilder app) => app.UseMvc(routes => routes.MapRoute("api", "{controller}/{action}")); [Route("customers")] public class CustomerApiController : ControllerBase { [HttpGet("{id:int}")] public IActionResult Get(int id) { } }
  35. Error Handling • Provides a developer exception page with detailed

    information about errors • Supports error page per status code or custom error pages • Supports custom error filter per action, controller or globally ASP.NET Core public void Configure(IApplicationBuilder app) { // Use in development only app.UseDeveloperExceptionPage(); // Simple status code error pages app.UseStatusCodePages(); // Custom error handler app.UseExceptionHandler("/error"); }
  36. Swagger Help Pages • NuGet: Swashbuckle.AspNetCore • Swagger: Generates a

    swagger.json containing all information about your Web APIs • Swagger UI: Provides a basic UI to view the swagger.json ASP.NET Core public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info() { Title = "Awesome Customer API", Version = "1.0" }); }); } public void Configure(IApplicationBuilder app) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("v1/swagger.json", "API v1"); }); }
  37. Swagger Help Pages ASP.NET Core

  38. • Built-in logging • Third party logging solutions (e.g. Serilog,

    Nlog) can be easily integrated without changing the logging API Logging public class SampleController : ControllerBase { private readonly ILogger<SampleController> _logger; public SampleController(ILogger<SampleController> logger) { _logger = logger; } [HttpGet] public IActionResult Get(int listId, string listName) { _logger?.LogDebug($"{nameof(SendListCreated)}: {{ListId}}, {{ListName}}", listId, listName); // Useful code } }
  39. • https://github.com/stefanprodan/AspNetCoreRateLimit • Public APIs • DDoS protection • Pay

    per request APIs • Rates for “expensive” resources • Rates per timeframe: Hour, Minute, Second • How? • IP-based • Account-based Rate Limiting public void ConfigureServices(IServiceCollection services) { services .Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting")) .Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies")) .AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>() .AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseIpRateLimiting(); }
  40. • Tell client that the data did not change •

    HTTP Status Code 304 • Two standardized options • Last Modified Header: Did data change since …? • Etag-Header: Checksum of data • e.g.: https://github.com/KevinDockx/HttpCacheHeaders Reduce traffic Initial request GET /item/1 OK 200 Etag: 123123123123 Following request GET /item/1 If-None-Match: 123123123123 304 Not Modified
  41. • Supported by all modern browsers • Supported by most

    servers • Reduces traffic by up to 70 % • But • more computation on server and client • is not always smaller • https://docs.microsoft.com/en-us/aspnet/core/performance/response-compression GZIP public void ConfigureServices(IServiceCollection services) { services.AddResponseCompression(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseResponseCompression(); }
  42. • Via URL (api.com/v1/item/1) • Via Accept-Header • Via Custom-Header

    • https://github.com/Microsoft/aspnet-api-versioning Versioning public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddApiVersioning(); } } [ApiVersion("1.0")] [Route("[controller]")] public class PeopleController : Controller { [HttpGet] public IActionRoute Get() => Ok(new[] { new Person() }); }
  43. • Hypermedia as the engine of application state • Navigate

    from anywhere to everywhere • Usage of API without prior knowledge HATEOAS
  44. • Example: POST • Example: Pagination HATEOAS POST /items 201

    Created Location: https://api.io/item/3 GET /items?page=1&size=10 200 OK Link: <https://api.io/items?page=2&size=10>; rel=“next”, <https://api.io/items?page=9&size=10>; rel=“last”
  45. • Bi-directional real time communication server <-> client • Integrates

    into the whole ASP.NET Core universe (Routing, Dependency Injection) • No jQuery dependency on client-side anymore!!!!111 • Usable not only on client-side JavaScript, but in Node.js or Web Workers • Browser support: IE 11, all evergreens SignalR
  46. Integrate into ASP.NET Core pipeline Add NuGet “Microsoft.AspNetCore.SignalR” (included by

    default) SignalR public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseSignalR(routes => { routes.MapHub<ChatHub>("/chathub"); }); }
  47. Hub • Represents a high-level pipeline allowing clients and servers

    to call methods on each other. • Bridges dispatching messages between client and server to easily call methods. • Strongly-typed parameters, model binding • Text protocol on JSON, binary protocol on MessagePack SignalR Hubs Endpoints Clients Transport WebSocket Server-Sent Events Long Polling Built on top of Text/Binary protocol via JSON/MessagePack public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }
  48. JavaScript Client • Install via NPM: @aspnet/signalr • Don’t forget

    CORS! • Possibility to receive and invoke methods • Third party modules for RxJS integration SignalR const connection = new signalR.HubConnection( "/chathub", { logger: signalR.LogLevel.Information }); connection.on("ReceiveMessage", (user, message) => { // Do something with the payload }); // Send something (e.g. via button) connection.invoke("SendMessage", user, message).catch(err => console.error); // Start the connection connection.start().catch(err => console.error);
  49. Summary • Unifies MVC and Web APIs • Built-in dependency

    injection • Cloud-ready • HTTP Pipeline & Configuration System • Several hosting options • Help Pages via Swagger • Don’t forget gzip, versioning, rate limiting, … • SignalR enables real time connections for server à client communication scenarios ASP.NET Core
  50. Lightweight & cross-platform data access EntityFramework Core

  51. Overview • Open source implementation: https://github.com/aspnet/EntityFramework • Lightweight & cross-platform

    version of .NET’s full-fledged EntityFramework • Support different database providers • MS SQL • SQLite • Postgres • InMemory • Offers Commandline Tools • Code First, Database First • Migrations EntityFramework Core
  52. Compared to EntityFramework 6 • EntityFramework Core does offer the

    following features, but some of them are still in development • Complex value types (without an ID) • Spatial data (geography, geometry) (in development) • Graphical visualization of the model (Model First) (in development) • Complex LINQ-Queries • Many-to-Many without a joining entity (in development) • Lazy Loading • Stored Procedures • Data Seeding (via model definition) • Full list available at https://docs.microsoft.com/en-us/ef/efcore-and-ef6/features EntityFramework Core
  53. NuGet Package & CLI • Core Package: Microsoft.EntityFrameworkCore • Database

    Provider Packages • MS SQL: Microsoft.EntityFrameworkCore.SqlServer • Postgres: Npgsql.EntityFrameworkCore.PostgreSQL • SQLite: Microsoft.EntityFrameworkCore.SQLite • CLI Tools • Powershell: Microsoft.EntityFrameworkCore.Tools • cmd/bash: Microsoft.EntityFrameworkCore.Tools.DotNet • Careful: cmd/bash is not installable via NuGet, only by editing .csproj directly EntityFramework Core
  54. Model • Working with EntityFramework Core is based on a

    model • A model is a simple class with specific attributes EntityFramework Core public class CustomerEntity { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } }
  55. Database Context • A database context (DbContext) represents a session

    to the database • Has one or multiple DbSet<EntityClass> to query the database • Allows executing raw SQL queries EntityFramework Core public class SampleApplicationDbContext : DbContext { public SampleApplicationDbContext(DbContextOptions options) : base(options) { // Overriding constructor is necessary to make use of DbContextOptions } public DbSet<CustomerEntity> Customers { get; set; } }
  56. Database Context • DbContext can be added to the dependency

    injection • Services can request a DbContext via constructor injection EntityFramework Core public void ConfigureServices(IServiceCollection services) { services.AddDbContext<SampleApplicationDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("SampleApplication")); } ); } public class CustomerService { public CustomerService(SampleApplicationDbContext dbContext) { } }
  57. Fluent API • Instead of using attributes to define a

    model, the Fluent API can be used • Fluent API has a richer API than the attributes for more complex scenarios EntityFramework Core public class SampleApplicationDbContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<CustomerEntity>() .HasKey(e => e.Id); modelBuilder.Entity<CustomerEntity>() .Property(e => e.FirstName) .IsRequired(); } }
  58. Querying • LINQ for database querying • Simple selects •

    Ordering • Grouping • Limiting • Can load related data • Eager Loading • Explicitly • Lazy Loading (soon) EntityFramework Core public class CustomerService { private readonly SampleApplicationDbContext _dbContext; public CustomerService(SampleApplicationDbContext dbContext) { _dbContext = dbContext; } public ICollection<CustomerEntity> List() { return _dbContext.Customers .OrderBy(c => c.Id) .ToList(); } }
  59. Saving • Tracks changes on entities per default • Writes

    back changes to the database when the DbContext is saved EntityFramework Core public void Update() { var entity = _dbContext.Customers.First(); entity.FirstName = "Max"; _dbContext.SaveChanges(); }
  60. Saving • Related data is also saved on change tracked

    entities • Use AsNoTracking() when querying to not track changes, e.g. for read-only scenarios EntityFramework Core public ICollection<CustomerEntity> List() { return _dbContext.Customers .AsNoTracking() .ToList(); }
  61. Transactions • SaveChanges() calls done as a transaction by default

    • Custom transaction can be create by using DbContext.Database.BeginTransaction() EntityFramework Core using (var transaction = _dbContext.Database.BeginTransaction()) { try { _dbContext.Customers.Add(new CustomerEntity { FirstName = "Max" }); _dbContext.SaveChanges(); _dbContext.Customers.Add(new CustomerEntity { FirstName = "Erika" }); _dbContext.SaveChanges(); transaction.Commit(); } catch (Exception e) { // TODO: Handle exception } }
  62. Migrations • Needs the PowerShell or cmd/bash tools installed •

    Add Migrations via • PowerShell: Add-Migration MigrationName • cmd/bash: dotnet ef migrations add MigrationName • Custom database initializer is needed to actually apply the migrations on startup EntityFramework Core public class DatabaseInitializer { public void Migrate() { _dbContext.Database.Migrate(); } }
  63. Summary • Cross-platform version of .NET’s EntityFramework • Not all

    features are supported yet • Offers PowerShell and cmd/bash toolings • Supports transactions & migrations • Support multiple database providers and an InMemory database for testing EntityFramework Core
  64. Better safe than sorry Unit & Integration Testing

  65. Overview • Well-known „.NET toolbelt“ can be used • Xunit,

    Nunit • Moq • Fluent Assertions • EntityFramework Core can be mocked for unit testing • Additionally it has an in-memory provider for integration testing Unit & Integration Testing
  66. Unit Testing Sample Unit & Integration Testing public class TodoServiceUnitTests

    { [Fact] public void GetAllLists_should_return_lists() { // in-memory sample-data data IQueryable<TodoList> lists = new List<TodoList>() { { Name = "First list" } }.AsQueryable(); var mockSet = new Mock<DbSet<TodoList>>(); mockSet.As<IQueryable<TodoList>>().Setup(m => m.Provider).Returns(lists.Provider); // ... more mock setup var mockContext = new Mock<TodoContext>(); mockContext.Setup(c => c.Lists).Returns(mockSet.Object); var sub = new TodoService(null, mockContext.Object); var actual = sub.GetAllLists(); actual.Should().NotBeEmpty().And.HaveCount(2); actual.First().Value.Should().Be("First list"); } }
  67. The cloud is just someone else‘s computer Deployment

  68. Docker • Docker is just one of multiple possibilities for

    deploying the application • Runs on • Microsoft Azure: Container Registry, Container Services, Container Instances • Amazon EC2 Container Services • Google Cloud Platform • IBM Bluemix Container Services • Runnable via • Simple Azure (Linux-based) Web Apps • Docker Swarm • Kubernetes Deployment
  69. Docker in Azure • Multi-step Docker Image • Build Image

    • Runtime Image • Runtime Image will be published to container host • Fully Continuous Delivery compatible Deployment # Build Image FROM microsoft/dotnet:2.1-sdk-alpine AS build-env WORKDIR /app COPY *.csproj ./ RUN dotnet restore COPY . ./ RUN dotnet publish -c Release -o out # Build runtime image FROM microsoft/dotnet:2.1-aspnetcore-runtime-alpine WORKDIR /app COPY --from=build-env /app/out . ENTRYPOINT ["dotnet", “MyApplication.dll"]
  70. Summary • .NET Standard makes it easier to create multi-targeting

    code (replacing PCLs) • .NET Standard 2.0 can target existing .NET code to reuse your existing business logic • ASP.NET Core provides a cross-platform possibility for MVC & Web APIs • ASP.NET Core is cloud-ready and offers a customizable HTTP pipeline • EntityFramework Core provides cross-platform and cross-database capabilities .NET Core & ASP.NET Core & EntityFramework Core
  71. .NET Standard MSDN https://docs.microsoft.com/en-us/dotnet/articles/standard/library Introducing .NET Standard https://blogs.msdn.microsoft.com/dotnet/2016/09/26/introducing-net-standard/ .NET Core

    MSDN https://docs.microsoft.com/en-us/dotnet/articles/core/ ASP.NET Core MVC MSDN https://docs.microsoft.com/en-us/aspnet/core/mvc/overview ASP.NET Core Web API MSDN https://docs.microsoft.com/en-us/aspnet/core/mvc/web-api/ EntityFramework Core MSDN https://docs.microsoft.com/en-us/ef/ Resources
  72. Thank you! Questions? Repository Sebastian Gingter Manuel Rauber @PhoenixHawk @manuelrauber

    Consultant Consultant https://github.com/thinktecture/api-ms-summit-spring-2019-services-dotnetcore