Skip to content

Commit ec4e76e

Browse files
authored
Merge pull request #478 from wisepotato/feat/#477
Resource hooks
2 parents 5941f19 + 6ccb1bb commit ec4e76e

File tree

103 files changed

+6362
-19642
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+6362
-19642
lines changed

JsonApiDotnetCore.sln

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
Microsoft Visual Studio Solution File, Format Version 12.00
32
# Visual Studio Version 16
43
VisualStudioVersion = 16.0.28606.126

README.md

+1-6
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,7 @@ Running tests locally requires access to a postgresql database.
9191
If you have docker installed, this can be propped up via:
9292

9393
```bash
94-
docker run --rm --name jsonapi-dotnet-core-testing \
95-
-e POSTGRES_DB=JsonApiDotNetCoreExample \
96-
-e POSTGRES_USER=postgres \
97-
-e POSTGRES_PASSWORD=postgres \
98-
-p 5432:5432 \
99-
postgres
94+
docker run --rm --name jsonapi-dotnet-core-testing -e POSTGRES_DB=JsonApiDotNetCoreExample -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres
10095
```
10196

10297
And then to run the tests:

docs/usage/extensibility/services.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ public class TodoItemService : EntityResourceService<TodoItem>
1717

1818
public TodoItemService(
1919
IJsonApiContext jsonApiContext,
20-
IEntityRepository<T, TId> repository,
20+
IEntityRepository<TodoItem, int> repository,
2121
ILoggerFactory loggerFactory,
2222
INotificationService notificationService)
2323
: base(jsonApiContext, repository, loggerFactory)
2424
{
2525
_notificationService = notificationService;
2626
}
2727

28-
public override async Task<TEntity> CreateAsync(TEntity entity)
28+
public override async Task<TodoItem> CreateAsync(TodoItem entity)
2929
{
3030
// call the base implementation which uses Entity Framework
3131
var newEntity = await base.CreateAsync(entity);

docs/usage/resources/hooks.md

+680
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:56042/",
7+
"sslPort": 0
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"GettingStarted": {
19+
"commandName": "Project",
20+
"launchBrowser": true
21+
}
22+
}
23+
}

src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs

+5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Internal;
23
using JsonApiDotNetCore.Models;
34

45
namespace GettingStarted.ResourceDefinitionExample
56
{
67
public class ModelDefinition : ResourceDefinition<Model>
78
{
9+
public ModelDefinition(IResourceGraph graph) : base(graph)
10+
{
11+
}
12+
813
// this allows POST / PATCH requests to set the value of a
914
// property, but we don't include this value in the response
1015
// this might be used if the incoming value gets hashed or
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using JsonApiDotNetCore.Controllers;
2+
using JsonApiDotNetCore.Services;
3+
using JsonApiDotNetCoreExample.Models;
4+
5+
namespace JsonApiDotNetCoreExample.Controllers
6+
{
7+
public class PassportsController : JsonApiController<Passport>
8+
{
9+
public PassportsController(
10+
IJsonApiContext jsonApiContext,
11+
IResourceService<Passport> resourceService)
12+
: base(jsonApiContext, resourceService)
13+
{ }
14+
}
15+
}

src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs

+29-2
Original file line numberDiff line numberDiff line change
@@ -41,33 +41,60 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
4141
modelBuilder.Entity<ArticleTag>()
4242
.HasKey(bc => new { bc.ArticleId, bc.TagId });
4343

44+
modelBuilder.Entity<IdentifiableArticleTag>()
45+
.HasKey(bc => new { bc.ArticleId, bc.TagId });
46+
47+
modelBuilder.Entity<Person>()
48+
.HasOne(t => t.StakeHolderTodo)
49+
.WithMany(t => t.StakeHolders)
50+
.HasForeignKey(t => t.StakeHolderTodoId)
51+
.OnDelete(DeleteBehavior.Cascade);
4452

4553
modelBuilder.Entity<TodoItem>()
4654
.HasOne(t => t.DependentTodoItem);
47-
55+
4856
modelBuilder.Entity<TodoItem>()
4957
.HasMany(t => t.ChildrenTodoItems)
5058
.WithOne(t => t.ParentTodoItem)
5159
.HasForeignKey(t => t.ParentTodoItemId);
5260

61+
modelBuilder.Entity<Person>()
62+
.HasOne(p => p.Passport)
63+
.WithOne(p => p.Person)
64+
.HasForeignKey<Person>(p => p.PassportId);
65+
66+
modelBuilder.Entity<Passport>()
67+
.HasOne(p => p.Person)
68+
.WithOne(p => p.Passport)
69+
.HasForeignKey<Person>(p => p.PassportId);
5370

71+
modelBuilder.Entity<TodoItem>()
72+
.HasOne(p => p.ToOnePerson)
73+
.WithOne(p => p.ToOneTodoItem)
74+
.HasForeignKey<TodoItem>(p => p.ToOnePersonId);
75+
76+
modelBuilder.Entity<Person>()
77+
.HasOne(p => p.ToOneTodoItem)
78+
.WithOne(p => p.ToOnePerson)
79+
.HasForeignKey<TodoItem>(p => p.ToOnePersonId);
5480
}
5581

5682
public DbSet<TodoItem> TodoItems { get; set; }
83+
public DbSet<Passport> Passports { get; set; }
5784
public DbSet<Person> People { get; set; }
5885
public DbSet<TodoItemCollection> TodoItemCollections { get; set; }
5986
public DbSet<CamelCasedModel> CamelCasedModels { get; set; }
6087
public DbSet<Article> Articles { get; set; }
6188
public DbSet<Author> Authors { get; set; }
6289
public DbSet<NonJsonApiResource> NonJsonApiResources { get; set; }
6390
public DbSet<User> Users { get; set; }
64-
6591
public DbSet<CourseEntity> Courses { get; set; }
6692
public DbSet<DepartmentEntity> Departments { get; set; }
6793
public DbSet<CourseStudentEntity> Registrations { get; set; }
6894
public DbSet<StudentEntity> Students { get; set; }
6995
public DbSet<PersonRole> PersonRoles { get; set; }
7096
public DbSet<ArticleTag> ArticleTags { get; set; }
97+
public DbSet<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
7198
public DbSet<Tag> Tags { get; set; }
7299
}
73100
}

src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj

100755100644
File mode changed.

src/Examples/JsonApiDotNetCoreExample/Models/Article.cs

+6
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,11 @@ public class Article : Identifiable
1717
[HasManyThrough(nameof(ArticleTags))]
1818
public List<Tag> Tags { get; set; }
1919
public List<ArticleTag> ArticleTags { get; set; }
20+
21+
22+
[NotMapped]
23+
[HasManyThrough(nameof(IdentifiableArticleTags))]
24+
public List<Tag> IdentifiableTags { get; set; }
25+
public List<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
2026
}
2127
}

src/Examples/JsonApiDotNetCoreExample/Models/ArticleTag.cs

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using JsonApiDotNetCore.Models;
2+
13
namespace JsonApiDotNetCoreExample.Models
24
{
35
public class ArticleTag
@@ -8,4 +10,18 @@ public class ArticleTag
810
public int TagId { get; set; }
911
public Tag Tag { get; set; }
1012
}
13+
14+
15+
public class IdentifiableArticleTag : Identifiable
16+
{
17+
public int ArticleId { get; set; }
18+
[HasOne("article")]
19+
public Article Article { get; set; }
20+
21+
public int TagId { get; set; }
22+
[HasOne("Tag")]
23+
public Tag Tag { get; set; }
24+
25+
public string SomeMetaData { get; set; }
26+
}
1127
}

src/Examples/JsonApiDotNetCoreExample/Models/Author.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ public class Author : Identifiable
1212
public List<Article> Articles { get; set; }
1313
}
1414
}
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace JsonApiDotNetCoreExample.Models
2+
{
3+
public interface IIsLockable
4+
{
5+
bool IsLocked { get; set; }
6+
}
7+
}

src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace JsonApiDotNetCoreExample.Models
55
public class Passport : Identifiable
66
{
77
public virtual int? SocialSecurityNumber { get; set; }
8+
public virtual bool IsLocked { get; set; }
9+
810
[HasOne("person")]
911
public virtual Person Person { get; set; }
1012
}

src/Examples/JsonApiDotNetCoreExample/Models/Person.cs

+17-5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ public class PersonRole : Identifiable
1111
public Person Person { get; set; }
1212
}
1313

14-
public class Person : Identifiable, IHasMeta
14+
public class Person : Identifiable, IHasMeta, IIsLockable
1515
{
16+
public bool IsLocked { get; set; }
17+
1618
[Attr("first-name")]
1719
public string FirstName { get; set; }
1820

@@ -32,22 +34,32 @@ public class Person : Identifiable, IHasMeta
3234
public virtual List<TodoItemCollection> TodoItemCollections { get; set; }
3335

3436
[HasOne("role")]
35-
public virtual PersonRole Role { get; set; }
37+
public virtual PersonRole Role { get; set; }
3638
public int? PersonRoleId { get; set; }
3739

40+
[HasOne("one-to-one-todo-item")]
41+
public virtual TodoItem ToOneTodoItem { get; set; }
42+
43+
44+
[HasOne("stake-holder-todo-item")]
45+
public virtual TodoItem StakeHolderTodo { get; set; }
46+
public virtual int? StakeHolderTodoId { get; set; }
47+
3848
[HasOne("unincludeable-item", documentLinks: Link.All, canInclude: false)]
3949
public virtual TodoItem UnIncludeableItem { get; set; }
4050

51+
public int? PassportId { get; set; }
52+
53+
[HasOne("passport")]
54+
public virtual Passport Passport { get; set; }
55+
4156
public Dictionary<string, object> GetMeta(IJsonApiContext context)
4257
{
4358
return new Dictionary<string, object> {
4459
{ "copyright", "Copyright 2015 Example Corp." },
4560
{ "authors", new string[] { "Jared Nance" } }
4661
};
4762
}
48-
public int? PassportId { get; set; }
49-
[HasOne("passport")]
50-
public virtual Passport Passport { get; set; }
5163

5264
}
5365
}

src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs

+14-4
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
namespace JsonApiDotNetCoreExample.Models
66
{
7-
public class TodoItem : Identifiable
7+
public class TodoItem : Identifiable, IIsLockable
88
{
99
public TodoItem()
1010
{
1111
GuidProperty = Guid.NewGuid();
1212
}
1313

14+
public bool IsLocked { get; set; }
15+
1416
[Attr("description")]
1517
public string Description { get; set; }
1618

@@ -48,17 +50,25 @@ public string CalculatedValue
4850
[HasOne("assignee")]
4951
public virtual Person Assignee { get; set; }
5052

53+
[HasOne("one-to-one-person")]
54+
public virtual Person ToOnePerson { get; set; }
55+
public virtual int? ToOnePersonId { get; set; }
56+
57+
58+
[HasMany("stake-holders")]
59+
public virtual List<Person> StakeHolders { get; set; }
60+
5161
[HasOne("collection")]
5262
public virtual TodoItemCollection Collection { get; set; }
5363

64+
65+
// cyclical to-one structure
5466
public virtual int? DependentTodoItemId { get; set; }
5567
[HasOne("dependent-on-todo")]
5668
public virtual TodoItem DependentTodoItem { get; set; }
5769

5870

59-
60-
61-
// cyclical structure
71+
// cyclical to-many structure
6272
public virtual int? ParentTodoItemId {get; set;}
6373
[HasOne("parent-todo")]
6474
public virtual TodoItem ParentTodoItem { get; set; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System;
4+
using JsonApiDotNetCore.Internal;
5+
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCore.Hooks;
7+
using JsonApiDotNetCoreExample.Models;
8+
using Microsoft.Extensions.Logging;
9+
using System.Security.Principal;
10+
11+
namespace JsonApiDotNetCoreExample.Resources
12+
{
13+
public class ArticleResource : ResourceDefinition<Article>
14+
{
15+
public ArticleResource(IResourceGraph graph) : base(graph) { }
16+
17+
public override IEnumerable<Article> OnReturn(HashSet<Article> entities, ResourcePipeline pipeline)
18+
{
19+
if (pipeline == ResourcePipeline.GetSingle && entities.Single().Name == "Classified")
20+
{
21+
throw new JsonApiException(403, "You are not allowed to see this article!", new UnauthorizedAccessException());
22+
}
23+
return entities.Where(t => t.Name != "This should be not be included");
24+
}
25+
}
26+
}
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using JsonApiDotNetCore.Internal;
5+
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCoreExample.Models;
7+
8+
namespace JsonApiDotNetCoreExample.Resources
9+
{
10+
public abstract class LockableResourceBase<T> : ResourceDefinition<T> where T : class, IIsLockable, IIdentifiable
11+
{
12+
protected LockableResourceBase(IResourceGraph graph) : base(graph) { }
13+
14+
protected void DisallowLocked(IEnumerable<T> entities)
15+
{
16+
foreach (var e in entities ?? Enumerable.Empty<T>())
17+
{
18+
if (e.IsLocked)
19+
{
20+
throw new JsonApiException(403, "Not allowed to update fields or relations of locked todo item", new UnauthorizedAccessException());
21+
}
22+
}
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)