Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ ServiceFabricBackup/
*.ldf
*.ndf

# SQLLite files
*.db

# Business Intelligence projects
*.rdl.data
*.bim.layout
Expand Down
15 changes: 12 additions & 3 deletions docker/docker-compose-aspnetcore/oats.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ input:
- path: /api/MsSql/ServerInfo
- path: /api/MsSql/Tables
- path: /api/Redis/LeftPush
- path: /api/todo/items
expected:
logs:
- logql: '{service_name="aspnetcore"} |~ `Application started.`'
- logql: '{ service_name="aspnetcore" } |~ `Application started.`'
contains:
- 'Application started'
metrics:
Expand Down Expand Up @@ -49,15 +50,15 @@ expected:
error.type: '500'
http.request.method: GET
http.response.status_code: '500'
- traceql: '{ span.http.route =~ "api/MsSql/ServerInfo"}'
- traceql: '{ span.http.route =~ "api/MsSql/ServerInfo" }'
spans:
- name: 'master'
attributes:
db.system: mssql
db.name: master
db.statement: sp_server_info
otel.library.name: OpenTelemetry.Instrumentation.SqlClient
- traceql: '{ span.http.route =~ "api/MsSql/Tables"}'
- traceql: '{ span.http.route =~ "api/MsSql/Tables" }'
spans:
- name: 'master'
attributes:
Expand All @@ -71,3 +72,11 @@ expected:
db.statement: LPUSH
db.system: redis
net.peer.name: redis
- traceql: '{ span.http.route =~ "/api/todo/items/" }'
spans:
- name: 'main'
attributes:
db.system: sqlite
db.name: main
peer.service: /app/App_Data/TodoApp.db
otel.library.name: OpenTelemetry.Instrumentation.EntityFrameworkCore
3 changes: 3 additions & 0 deletions examples/net8.0/aspnetcore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
//

using aspnetcore;
using Grafana.OpenTelemetry;
using Microsoft.Data.SqlClient;
using OpenTelemetry.Logs;
Expand Down Expand Up @@ -31,6 +32,7 @@
builder.Services.AddHttpClient();

builder.Services.AddControllers();
builder.Services.AddTodoApp();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
Expand All @@ -43,6 +45,7 @@

app.UseAuthorization();
app.MapControllers();
app.MapTodoApp();

app.MapGet("/", () => Results.Redirect("/swagger"));

Expand Down
179 changes: 179 additions & 0 deletions examples/net8.0/aspnetcore/TodoAppEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//
// Copyright Grafana Labs
// SPDX-License-Identifier: Apache-2.0
//

using Microsoft.EntityFrameworkCore;

namespace aspnetcore;

public static class TodoAppEndpoints
{
public static IServiceCollection AddTodoApp(this IServiceCollection services)
{
services.AddSingleton(TimeProvider.System);
services.AddScoped<TodoRepository>();

services.AddDbContext<TodoContext>((serviceProvider, options) =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
var dataDirectory = configuration["DataDirectory"];

if (string.IsNullOrEmpty(dataDirectory) || !Path.IsPathRooted(dataDirectory))
{
var environment = serviceProvider.GetRequiredService<IHostEnvironment>();
dataDirectory = Path.Combine(environment.ContentRootPath, "App_Data");
}

if (!Directory.Exists(dataDirectory))
{
Directory.CreateDirectory(dataDirectory);
}

var databaseFile = Path.Combine(dataDirectory, "TodoApp.db");

options.UseSqlite("Data Source=" + databaseFile);
});

return services;
}

public static IEndpointRouteBuilder MapTodoApp(this IEndpointRouteBuilder builder)
{
var todos = builder.MapGroup("/api/todo/items").WithTags("Todo");
{
todos.MapPost("/", async (CreateTodoItemModel model, TodoRepository repository) =>
{
var id = await repository.AddItemAsync(model.Text);
return Results.Created($"/api/items/{id}", new { Id = id });
});

todos.MapGet("/", async (TodoRepository repository) => await repository.GetItemsAsync());

todos.MapGet("/{id}", async (Guid id, TodoRepository repository) => await repository.GetItemAsync(id));

todos.MapPost("/{id}/complete", async (Guid id, TodoRepository repository) =>
{
return await repository.CompleteItemAsync(id) switch
{
true => Results.NoContent(),
false => Results.Problem(statusCode: StatusCodes.Status400BadRequest),
_ => Results.Problem(statusCode: StatusCodes.Status404NotFound),
};
});

todos.MapDelete("/{id}", async (Guid id, TodoRepository repository) =>
{
var deleted = await repository.DeleteItemAsync(id);
return deleted switch
{
true => Results.NoContent(),
false => Results.Problem(statusCode: StatusCodes.Status404NotFound),
};
});
}

return builder;
}

public class TodoContext(DbContextOptions<TodoContext> options) : DbContext(options)
{
public DbSet<TodoItem> Items { get; set; } = default!;
}

public class TodoRepository(TimeProvider timeProvider, TodoContext context)
{
public async Task<TodoItem> AddItemAsync(string text)
{
await this.EnsureDatabaseAsync();

var item = new TodoItem
{
CreatedAt = this.UtcNow(),
Text = text
};

context.Add(item);

await context.SaveChangesAsync();

return item;
}

public async Task<bool?> CompleteItemAsync(Guid itemId)
{
var item = await this.GetItemAsync(itemId);

if (item is null)
{
return null;
}

if (item.CompletedAt.HasValue)
{
return false;
}

item.CompletedAt = this.UtcNow();

context.Items.Update(item);

await context.SaveChangesAsync();

return true;
}

public async Task<bool> DeleteItemAsync(Guid itemId)
{
var item = await this.GetItemAsync(itemId);

if (item is null)
{
return false;
}

context.Items.Remove(item);

await context.SaveChangesAsync();

return true;
}

public async Task<TodoItem?> GetItemAsync(Guid itemId)
{
await this.EnsureDatabaseAsync();

return await context.Items.FindAsync([itemId]);
}

public async Task<IList<TodoItem>> GetItemsAsync()
{
await this.EnsureDatabaseAsync();

return await context.Items
.OrderBy(x => x.CompletedAt.HasValue)
.ThenBy(x => x.CreatedAt)
.ToListAsync();
}

private async Task EnsureDatabaseAsync() => await context.Database.EnsureCreatedAsync();

private DateTime UtcNow() => timeProvider.GetUtcNow().UtcDateTime;
}

public class CreateTodoItemModel
{
public string Text { get; set; } = string.Empty;
}

public class TodoItem
{
public Guid Id { get; set; }

public string Text { get; set; } = default!;

public DateTime CreatedAt { get; set; }

public DateTime? CompletedAt { get; set; }
}
}
3 changes: 2 additions & 1 deletion examples/net8.0/aspnetcore/aspnetcore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@

<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.16" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading