Skip to content

Update examples #1269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion docs/internals/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Processing a request involves the following steps:
To get a sense of what this all looks like, let's look at an example query string:

```
/api/v1/blogs?
/api/blogs?
include=owner,posts.comments.author&
filter=has(posts)&
sort=count(posts)&
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ options.UseRelativeLinks = true;
"relationships": {
"author": {
"links": {
"self": "/api/v1/articles/4309/relationships/author",
"related": "/api/v1/articles/4309/author"
"self": "/articles/4309/relationships/author",
"related": "/articles/4309/author"
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions docs/usage/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ You can add a namespace to all URLs by specifying it at startup.

```c#
// Program.cs
builder.Services.AddJsonApi<AppDbContext>(options => options.Namespace = "api/v1");
builder.Services.AddJsonApi<AppDbContext>(options => options.Namespace = "api/shopping");
```

Which results in URLs like: https://yourdomain.com/api/v1/people
Which results in URLs like: https://yourdomain.com/api/shopping/articles

## Default routing convention

Expand Down Expand Up @@ -66,14 +66,14 @@ It is possible to override the default routing convention for an auto-generated
```c#
// Auto-generated
[DisableRoutingConvention]
[Route("v1/custom/route/summaries-for-orders")]
[Route("custom/route/summaries-for-orders")]
partial class OrderSummariesController
{
}

// Hand-written
[DisableRoutingConvention]
[Route("v1/custom/route/lines-in-order")]
[Route("custom/route/lines-in-order")]
public class OrderLineController : JsonApiController<OrderLine, int>
{
public OrderLineController(IJsonApiOptions options, IResourceGraph resourceGraph,
Expand Down
2 changes: 1 addition & 1 deletion src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
private string GetConnectionString()
{
string? tenantName = GetTenantName();
string? connectionString = _configuration[$"Data:{tenantName ?? "Default"}Connection"];
string? connectionString = _configuration.GetConnectionString(tenantName ?? "Default");

if (connectionString == null)
{
Expand Down
8 changes: 4 additions & 4 deletions src/Examples/DatabasePerTenantExample/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"Data": {
"DefaultConnection": "Host=localhost;Port=5432;Database=DefaultTenantDb;User ID=postgres;Password=###",
"AdventureWorksConnection": "Host=localhost;Port=5432;Database=AdventureWorks;User ID=postgres;Password=###",
"ContosoConnection": "Host=localhost;Port=5432;Database=Contoso;User ID=postgres;Password=###"
"ConnectionStrings": {
"Default": "Host=localhost;Database=DefaultTenantDb;User ID=postgres;Password=###;Include Error Detail=true",
"AdventureWorks": "Host=localhost;Database=AdventureWorks;User ID=postgres;Password=###;Include Error Detail=true",
"Contoso": "Host=localhost;Database=Contoso;User ID=postgres;Password=###;Include Error Detail=true"
},
"Logging": {
"LogLevel": {
Expand Down
32 changes: 26 additions & 6 deletions src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using JsonApiDotNetCoreExample.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

// @formatter:wrap_chained_method_calls chop_always

Expand All @@ -18,14 +19,33 @@ public AppDbContext(DbContextOptions<AppDbContext> options)

protected override void OnModelCreating(ModelBuilder builder)
{
// When deleting a person, un-assign him/her from existing todo items.
// When deleting a person, un-assign him/her from existing todo-items.
builder.Entity<Person>()
.HasMany(person => person.AssignedTodoItems)
.WithOne(todoItem => todoItem.Assignee!);
.WithOne(todoItem => todoItem.Assignee);

// When deleting a person, the todo items he/she owns are deleted too.
builder.Entity<TodoItem>()
.HasOne(todoItem => todoItem.Owner)
.WithMany();
// When deleting a person, the todo-items he/she owns are deleted too.
builder.Entity<Person>()
.HasMany(person => person.OwnedTodoItems)
.WithOne(todoItem => todoItem.Owner);

AdjustDeleteBehaviorForJsonApi(builder);
}

private static void AdjustDeleteBehaviorForJsonApi(ModelBuilder builder)
{
foreach (IMutableForeignKey foreignKey in builder.Model.GetEntityTypes()
.SelectMany(entityType => entityType.GetForeignKeys()))
{
if (foreignKey.DeleteBehavior == DeleteBehavior.ClientSetNull)
{
foreignKey.DeleteBehavior = DeleteBehavior.SetNull;
}

if (foreignKey.DeleteBehavior == DeleteBehavior.ClientCascade)
{
foreignKey.DeleteBehavior = DeleteBehavior.Cascade;
}
}
}
}
35 changes: 35 additions & 0 deletions src/Examples/JsonApiDotNetCoreExample/Data/RotatingList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace JsonApiDotNetCoreExample.Data;

internal abstract class RotatingList
{
public static RotatingList<T> Create<T>(int count, Func<int, T> createElement)
{
List<T> elements = new();

for (int index = 0; index < count; index++)
{
T element = createElement(index);
elements.Add(element);
}

return new RotatingList<T>(elements);
}
}

internal sealed class RotatingList<T>
{
private int _index = -1;

public IList<T> Elements { get; }

public RotatingList(IList<T> elements)
{
Elements = elements;
}

public T GetNext()
{
_index++;
return Elements[_index % Elements.Count];
}
}
56 changes: 56 additions & 0 deletions src/Examples/JsonApiDotNetCoreExample/Data/Seeder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using JetBrains.Annotations;
using JsonApiDotNetCoreExample.Models;

namespace JsonApiDotNetCoreExample.Data;

[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
internal sealed class Seeder
{
public static async Task CreateSampleDataAsync(AppDbContext dbContext)
{
const int todoItemCount = 500;
const int personCount = 50;
const int tagCount = 25;

RotatingList<Person> people = RotatingList.Create(personCount, index => new Person
{
FirstName = $"FirstName{index + 1:D2}",
LastName = $"LastName{index + 1:D2}"
});

RotatingList<Tag> tags = RotatingList.Create(tagCount, index => new Tag
{
Name = $"TagName{index + 1:D2}"
});

RotatingList<TodoItemPriority> priorities = RotatingList.Create(3, index => (TodoItemPriority)(index + 1));

RotatingList<TodoItem> todoItems = RotatingList.Create(todoItemCount, index =>
{
var todoItem = new TodoItem
{
Description = $"TodoItem{index + 1:D3}",
Priority = priorities.GetNext(),
DurationInHours = index,
CreatedAt = DateTimeOffset.UtcNow,
Owner = people.GetNext(),
Tags = new HashSet<Tag>
{
tags.GetNext(),
tags.GetNext(),
tags.GetNext()
}
};

if (index % 3 == 0)
{
todoItem.Assignee = people.GetNext();
}

return todoItem;
});

dbContext.TodoItems.AddRange(todoItems.Elements);
await dbContext.SaveChangesAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace JsonApiDotNetCoreExample.Definitions;

[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
public sealed class TodoItemDefinition : JsonApiResourceDefinition<TodoItem, int>
public sealed class TodoItemDefinition : JsonApiResourceDefinition<TodoItem, long>
{
private readonly ISystemClock _systemClock;

Expand All @@ -29,7 +29,7 @@ private SortExpression GetDefaultSortOrder()
{
return CreateSortExpressionFromLambda(new PropertySortOrder
{
(todoItem => todoItem.Priority, ListSortDirection.Descending),
(todoItem => todoItem.Priority, ListSortDirection.Ascending),
(todoItem => todoItem.LastModifiedAt, ListSortDirection.Descending)
});
}
Expand Down
10 changes: 9 additions & 1 deletion src/Examples/JsonApiDotNetCoreExample/Models/Person.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
Expand All @@ -6,14 +7,21 @@ namespace JsonApiDotNetCoreExample.Models;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
[Resource]
public sealed class Person : Identifiable<int>
public sealed class Person : Identifiable<long>
{
[Attr]
public string? FirstName { get; set; }

[Attr]
public string LastName { get; set; } = null!;

[Attr(Capabilities = AttrCapabilities.AllowView)]
[NotMapped]
public string DisplayName => FirstName != null ? $"{FirstName} {LastName}" : LastName;

[HasMany]
public ISet<TodoItem> OwnedTodoItems { get; set; } = new HashSet<TodoItem>();

[HasMany]
public ISet<TodoItem> AssignedTodoItems { get; set; } = new HashSet<TodoItem>();
}
2 changes: 1 addition & 1 deletion src/Examples/JsonApiDotNetCoreExample/Models/Tag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace JsonApiDotNetCoreExample.Models;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
[Resource]
public sealed class Tag : Identifiable<int>
public sealed class Tag : Identifiable<long>
{
[Attr]
[MinLength(1)]
Expand Down
9 changes: 6 additions & 3 deletions src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace JsonApiDotNetCoreExample.Models;

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
[Resource]
public sealed class TodoItem : Identifiable<int>
public sealed class TodoItem : Identifiable<long>
{
[Attr]
public string Description { get; set; } = null!;
Expand All @@ -16,6 +16,9 @@ public sealed class TodoItem : Identifiable<int>
[Required]
public TodoItemPriority? Priority { get; set; }

[Attr]
public long? DurationInHours { get; set; }

[Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)]
public DateTimeOffset CreatedAt { get; set; }

Expand All @@ -25,9 +28,9 @@ public sealed class TodoItem : Identifiable<int>
[HasOne]
public Person Owner { get; set; } = null!;

[HasOne(Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowSet)]
[HasOne]
public Person? Assignee { get; set; }

[HasMany(Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowFilter)]
[HasMany]
public ISet<Tag> Tags { get; set; } = new HashSet<Tag>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace JsonApiDotNetCoreExample.Models;
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
public enum TodoItemPriority
{
Low,
Medium,
High
High = 1,
Medium = 2,
Low = 3
}
10 changes: 7 additions & 3 deletions src/Examples/JsonApiDotNetCoreExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ static void ConfigureServices(WebApplicationBuilder builder)
builder.Services.AddDbContext<AppDbContext>(options =>
{
string? connectionString = GetConnectionString(builder.Configuration);

options.UseNpgsql(connectionString);

#if DEBUG
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
Expand All @@ -60,11 +60,12 @@ static void ConfigureServices(WebApplicationBuilder builder)
{
builder.Services.AddJsonApi<AppDbContext>(options =>
{
options.Namespace = "api/v1";
options.Namespace = "api";
options.UseRelativeLinks = true;
options.IncludeTotalResourceCount = true;
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());

#if DEBUG
options.IncludeExceptionStackTraceInErrors = true;
options.IncludeRequestBodyInErrors = true;
Expand All @@ -76,7 +77,7 @@ static void ConfigureServices(WebApplicationBuilder builder)
static string? GetConnectionString(IConfiguration configuration)
{
string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres";
return configuration["Data:DefaultConnection"]?.Replace("###", postgresPassword);
return configuration.GetConnectionString("Default")?.Replace("###", postgresPassword);
}

static void ConfigurePipeline(WebApplication webApplication)
Expand All @@ -98,5 +99,8 @@ static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();

var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();

await Seeder.CreateSampleDataAsync(dbContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"launchUrl": "api/v1/todoItems",
"launchBrowser": true,
"launchUrl": "api/todoItems?include=tags&filter=equals(priority,'High')",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Kestrel": {
"commandName": "Project",
"launchBrowser": false,
"launchUrl": "api/v1/todoItems",
"launchBrowser": true,
"launchUrl": "api/todoItems?include=tags&filter=equals(priority,'High')",
"applicationUrl": "https://localhost:44340;http://localhost:14140",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand Down
4 changes: 2 additions & 2 deletions src/Examples/JsonApiDotNetCoreExample/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Data": {
"DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=###"
"ConnectionStrings": {
"Default": "Host=localhost;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=###;Include Error Detail=true"
},
"Logging": {
"LogLevel": {
Expand Down
4 changes: 2 additions & 2 deletions src/Examples/NoEntityFrameworkExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
string? connectionString = GetConnectionString(builder.Configuration);
builder.Services.AddNpgsql<AppDbContext>(connectionString);

builder.Services.AddJsonApi(options => options.Namespace = "api/v1", resources: resourceGraphBuilder => resourceGraphBuilder.Add<WorkItem, int>());
builder.Services.AddJsonApi(options => options.Namespace = "api", resources: resourceGraphBuilder => resourceGraphBuilder.Add<WorkItem, int>());

builder.Services.AddResourceService<WorkItemService>();

Expand All @@ -29,7 +29,7 @@
static string? GetConnectionString(IConfiguration configuration)
{
string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres";
return configuration["Data:DefaultConnection"]?.Replace("###", postgresPassword);
return configuration.GetConnectionString("Default")?.Replace("###", postgresPassword);
}

static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
Expand Down
Loading