();
+
+builder.Services.AddMvc();
+
+
+var app = builder.Build();
+
+
+// Configure the HTTP request pipeline.
+
+app.MapDefaultControllerRoute();
+
+app.Run();
diff --git a/samples/net8.0/FrontendWeb/Properties/launchSettings.json b/samples/net8.0/FrontendWeb/Properties/launchSettings.json
new file mode 100644
index 0000000..8870b99
--- /dev/null
+++ b/samples/net8.0/FrontendWeb/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "FrontendWeb": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "launchUrl": "http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/net8.0/FrontendWeb/Views/Home/Index.cshtml b/samples/net8.0/FrontendWeb/Views/Home/Index.cshtml
new file mode 100644
index 0000000..8948537
--- /dev/null
+++ b/samples/net8.0/FrontendWeb/Views/Home/Index.cshtml
@@ -0,0 +1,8 @@
+
+
+FrontendWeb
+FrontendWeb
+This is the beautiful web frontend.
+Refresh this page a few times and check the Jaeger UI - you should already see traces!
+
+Place an order
diff --git a/samples/net8.0/FrontendWeb/Views/Home/PlaceOrder.cshtml b/samples/net8.0/FrontendWeb/Views/Home/PlaceOrder.cshtml
new file mode 100644
index 0000000..70a6ebc
--- /dev/null
+++ b/samples/net8.0/FrontendWeb/Views/Home/PlaceOrder.cshtml
@@ -0,0 +1,13 @@
+@model Shared.PlaceOrderCommand
+
+
+FrontendWeb
+FrontendWeb
+Place an order
+
+
diff --git a/samples/net8.0/FrontendWeb/Views/_ViewImports.cshtml b/samples/net8.0/FrontendWeb/Views/_ViewImports.cshtml
new file mode 100644
index 0000000..1113d34
--- /dev/null
+++ b/samples/net8.0/FrontendWeb/Views/_ViewImports.cshtml
@@ -0,0 +1,2 @@
+@using FrontendWeb
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/samples/net8.0/FrontendWeb/appsettings.json b/samples/net8.0/FrontendWeb/appsettings.json
new file mode 100644
index 0000000..36ea555
--- /dev/null
+++ b/samples/net8.0/FrontendWeb/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information",
+ "OpenTracing": "Debug"
+ }
+ }
+}
diff --git a/samples/net8.0/OrdersApi/Controllers/OrdersController.cs b/samples/net8.0/OrdersApi/Controllers/OrdersController.cs
new file mode 100644
index 0000000..aa0095c
--- /dev/null
+++ b/samples/net8.0/OrdersApi/Controllers/OrdersController.cs
@@ -0,0 +1,76 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+using OpenTracing;
+using OrdersApi.DataStore;
+using Shared;
+
+namespace OrdersApi.Controllers;
+
+[Route("orders")]
+public class OrdersController : Controller
+{
+ private readonly OrdersDbContext _dbContext;
+ private readonly HttpClient _httpClient;
+ private readonly ITracer _tracer;
+
+ public OrdersController(OrdersDbContext dbContext, HttpClient httpClient, ITracer tracer)
+ {
+ _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
+ _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
+ _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
+ }
+
+ [HttpGet]
+ public async Task Index()
+ {
+ var orders = await _dbContext.Orders.ToListAsync();
+
+ return Ok(orders.Select(x => new { x.OrderId }).ToList());
+ }
+
+ [HttpPost]
+ public async Task Index([FromBody] PlaceOrderCommand cmd)
+ {
+ var customer = await GetCustomer(cmd.CustomerId);
+
+ var order = new Order
+ {
+ CustomerId = cmd.CustomerId,
+ ItemNumber = cmd.ItemNumber,
+ Quantity = cmd.Quantity
+ };
+
+ _dbContext.Orders.Add(order);
+
+ await _dbContext.SaveChangesAsync();
+
+ _tracer.ActiveSpan?.Log(new Dictionary {
+ { "event", "OrderPlaced" },
+ { "orderId", order.OrderId },
+ { "customer", order.CustomerId },
+ { "customer_name", customer.Name },
+ { "item_number", order.ItemNumber },
+ { "quantity", order.Quantity }
+ });
+
+ return Ok();
+ }
+
+ private async Task GetCustomer(int customerId)
+ {
+ var request = new HttpRequestMessage
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new Uri(Constants.CustomersUrl + "customers/" + customerId)
+ };
+
+ var response = await _httpClient.SendAsync(request);
+
+ response.EnsureSuccessStatusCode();
+
+ var body = await response.Content.ReadAsStringAsync();
+
+ return JsonConvert.DeserializeObject(body);
+ }
+}
diff --git a/samples/net8.0/OrdersApi/DataStore/Order.cs b/samples/net8.0/OrdersApi/DataStore/Order.cs
new file mode 100644
index 0000000..b2572fd
--- /dev/null
+++ b/samples/net8.0/OrdersApi/DataStore/Order.cs
@@ -0,0 +1,17 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace OrdersApi.DataStore;
+
+public class Order
+{
+ [Key]
+ public int OrderId { get; set; }
+
+ public int CustomerId { get; set; }
+
+ [Required, StringLength(10)]
+ public string ItemNumber { get; set; } = string.Empty;
+
+ [Required, Range(1, 100)]
+ public int Quantity { get; set; }
+}
diff --git a/samples/net8.0/OrdersApi/DataStore/OrdersDbContext.cs b/samples/net8.0/OrdersApi/DataStore/OrdersDbContext.cs
new file mode 100644
index 0000000..730384a
--- /dev/null
+++ b/samples/net8.0/OrdersApi/DataStore/OrdersDbContext.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace OrdersApi.DataStore;
+
+public class OrdersDbContext : DbContext
+{
+ public OrdersDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public DbSet Orders => Set();
+
+ public void Seed()
+ {
+ if (Database.EnsureCreated())
+ {
+ Database.Migrate();
+
+ SaveChanges();
+ }
+ }
+}
diff --git a/samples/net8.0/OrdersApi/OrdersApi.csproj b/samples/net8.0/OrdersApi/OrdersApi.csproj
new file mode 100644
index 0000000..965d17c
--- /dev/null
+++ b/samples/net8.0/OrdersApi/OrdersApi.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/net8.0/OrdersApi/Program.cs b/samples/net8.0/OrdersApi/Program.cs
new file mode 100644
index 0000000..01e56d1
--- /dev/null
+++ b/samples/net8.0/OrdersApi/Program.cs
@@ -0,0 +1,57 @@
+using Microsoft.EntityFrameworkCore;
+using OrdersApi.DataStore;
+using Shared;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.WebHost.UseUrls(Constants.OrdersUrl);
+
+// Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions)
+builder.Services.AddJaeger();
+
+// Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core
+builder.Services.AddOpenTracing(builder =>
+{
+ builder.ConfigureAspNetCore(options =>
+ {
+ // We don't need any tracing data for our health endpoint.
+ options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health");
+ });
+});
+
+// Adds a SqlServer DB to show EFCore traces.
+builder.Services.AddDbContext(options =>
+{
+ options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Orders-net5;Trusted_Connection=True;MultipleActiveResultSets=true");
+});
+
+builder.Services.AddSingleton();
+
+builder.Services.AddMvc();
+
+builder.Services.AddHealthChecks()
+ .AddDbContextCheck();
+
+
+var app = builder.Build();
+
+
+// Load some dummy data into the db.
+using (var scope = app.Services.CreateScope())
+{
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ dbContext.Seed();
+}
+
+
+// Configure the HTTP request pipeline.
+
+app.MapGet("/", () => "Orders API");
+
+app.MapHealthChecks("/health");
+
+app.MapDefaultControllerRoute();
+
+app.Run();
diff --git a/samples/net8.0/OrdersApi/Properties/launchSettings.json b/samples/net8.0/OrdersApi/Properties/launchSettings.json
new file mode 100644
index 0000000..6b818d0
--- /dev/null
+++ b/samples/net8.0/OrdersApi/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "OrdersApi": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "launchUrl": "http://localhost:5002",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/samples/net8.0/OrdersApi/appsettings.json b/samples/net8.0/OrdersApi/appsettings.json
new file mode 100644
index 0000000..723c096
--- /dev/null
+++ b/samples/net8.0/OrdersApi/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/samples/net8.0/Shared/Constants.cs b/samples/net8.0/Shared/Constants.cs
new file mode 100644
index 0000000..9979429
--- /dev/null
+++ b/samples/net8.0/Shared/Constants.cs
@@ -0,0 +1,10 @@
+namespace Shared;
+
+public class Constants
+{
+ public const string FrontendUrl = "http://localhost:5000/";
+
+ public const string CustomersUrl = "http://localhost:5001/";
+
+ public const string OrdersUrl = "http://localhost:5002/";
+}
diff --git a/samples/net8.0/Shared/Customer.cs b/samples/net8.0/Shared/Customer.cs
new file mode 100644
index 0000000..48248f5
--- /dev/null
+++ b/samples/net8.0/Shared/Customer.cs
@@ -0,0 +1,19 @@
+namespace Shared;
+
+public class Customer
+{
+ public int CustomerId { get; set; }
+ public string Name { get; set; }
+
+ public Customer()
+ {
+ CustomerId = 0;
+ Name = string.Empty;
+ }
+
+ public Customer(int customerId, string name)
+ {
+ CustomerId = customerId;
+ Name = name;
+ }
+}
diff --git a/samples/net8.0/Shared/JaegerServiceCollectionExtensions.cs b/samples/net8.0/Shared/JaegerServiceCollectionExtensions.cs
new file mode 100644
index 0000000..b2f8f91
--- /dev/null
+++ b/samples/net8.0/Shared/JaegerServiceCollectionExtensions.cs
@@ -0,0 +1,53 @@
+using System.Reflection;
+using Jaeger;
+using Jaeger.Reporters;
+using Jaeger.Samplers;
+using Jaeger.Senders.Thrift;
+using Microsoft.Extensions.Logging;
+using OpenTracing;
+using OpenTracing.Contrib.NetCore.Configuration;
+using OpenTracing.Util;
+
+namespace Microsoft.Extensions.DependencyInjection;
+
+public static class JaegerServiceCollectionExtensions
+{
+ private static readonly Uri _jaegerUri = new Uri("http://localhost:14268/api/traces");
+
+ public static IServiceCollection AddJaeger(this IServiceCollection services)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ services.AddSingleton(serviceProvider =>
+ {
+ string serviceName = Assembly.GetEntryAssembly()?.GetName().Name ?? "unknown-service";
+
+ ILoggerFactory loggerFactory = serviceProvider.GetRequiredService();
+
+ ISampler sampler = new ConstSampler(sample: true);
+
+ IReporter reporter = new RemoteReporter.Builder()
+ .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build())
+ .Build();
+
+ ITracer tracer = new Tracer.Builder(serviceName)
+ .WithLoggerFactory(loggerFactory)
+ .WithSampler(sampler)
+ .WithReporter(reporter)
+ .Build();
+
+ GlobalTracer.Register(tracer);
+
+ return tracer;
+ });
+
+ // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger.
+ services.Configure(options =>
+ {
+ options.IgnorePatterns.Add(request => request.RequestUri != null && _jaegerUri.IsBaseOf(request.RequestUri));
+ });
+
+ return services;
+ }
+}
diff --git a/samples/net8.0/Shared/PlaceOrderCommand.cs b/samples/net8.0/Shared/PlaceOrderCommand.cs
new file mode 100644
index 0000000..a538e2c
--- /dev/null
+++ b/samples/net8.0/Shared/PlaceOrderCommand.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Shared;
+
+public class PlaceOrderCommand
+{
+ [Required, Range(1, int.MaxValue)]
+ public int CustomerId { get; set; }
+
+ [Required, StringLength(10)]
+ public string ItemNumber { get; set; } = string.Empty;
+
+ [Required, Range(1, 100)]
+ public int Quantity { get; set; }
+}
diff --git a/samples/net8.0/Shared/Shared.csproj b/samples/net8.0/Shared/Shared.csproj
new file mode 100644
index 0000000..ac57a2c
--- /dev/null
+++ b/samples/net8.0/Shared/Shared.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/net8.0/TrafficGenerator/Program.cs b/samples/net8.0/TrafficGenerator/Program.cs
new file mode 100644
index 0000000..23f4934
--- /dev/null
+++ b/samples/net8.0/TrafficGenerator/Program.cs
@@ -0,0 +1,15 @@
+using TrafficGenerator;
+
+using var host = Host.CreateDefaultBuilder(args)
+ .ConfigureServices((hostContext, services) =>
+ {
+ // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions)
+ services.AddJaeger();
+
+ services.AddOpenTracing();
+
+ services.AddHostedService();
+ })
+ .Build();
+
+await host.RunAsync();
diff --git a/samples/net8.0/TrafficGenerator/TrafficGenerator.csproj b/samples/net8.0/TrafficGenerator/TrafficGenerator.csproj
new file mode 100644
index 0000000..78c7ee3
--- /dev/null
+++ b/samples/net8.0/TrafficGenerator/TrafficGenerator.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/samples/net8.0/TrafficGenerator/Worker.cs b/samples/net8.0/TrafficGenerator/Worker.cs
new file mode 100644
index 0000000..0717557
--- /dev/null
+++ b/samples/net8.0/TrafficGenerator/Worker.cs
@@ -0,0 +1,51 @@
+using Shared;
+
+namespace TrafficGenerator;
+
+public class Worker : BackgroundService
+{
+ private readonly ILogger _logger;
+
+ public Worker(ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ HttpClient customersHttpClient = new HttpClient();
+ customersHttpClient.BaseAddress = new Uri(Constants.CustomersUrl);
+
+ HttpClient ordersHttpClient = new HttpClient();
+ ordersHttpClient.BaseAddress = new Uri(Constants.OrdersUrl);
+
+
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ HttpResponseMessage ordersHealthResponse = await ordersHttpClient.GetAsync("health");
+ _logger.LogInformation($"Health of 'orders'-endpoint: '{ordersHealthResponse.StatusCode}'");
+
+ HttpResponseMessage customersHealthResponse = await customersHttpClient.GetAsync("health");
+ _logger.LogInformation($"Health of 'customers'-endpoint: '{customersHealthResponse.StatusCode}'");
+
+ _logger.LogInformation("Requesting customers");
+
+ HttpResponseMessage response = await customersHttpClient.GetAsync("customers");
+
+ _logger.LogInformation($"Response was '{response.StatusCode}'");
+
+ await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ /* Application should be stopped -> no-op */
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Unhandled exception");
+ }
+ }
+}
diff --git a/samples/net8.0/TrafficGenerator/appsettings.json b/samples/net8.0/TrafficGenerator/appsettings.json
new file mode 100644
index 0000000..789de74
--- /dev/null
+++ b/samples/net8.0/TrafficGenerator/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.AspNetCore.Hosting": "Information"
+ }
+ }
+}