Skip to content

Commit 8dd97a9

Browse files
spike83csantero
authored andcommitted
Feat: added handler methods to intercept jsonapi entity framework (#116)
* Feat: added handler methods to intercept jsonapi entity framework for custom entity manipulations * make the dbcontext accessible to subclasses for interception * @csantero implemented your suggestions * implemented as void method
1 parent 5ae40e8 commit 8dd97a9

File tree

2 files changed

+85
-15
lines changed

2 files changed

+85
-15
lines changed

JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace JSONAPI.EntityFramework.Http
1818
/// </summary>
1919
public class EntityFrameworkDocumentMaterializer<T> : IDocumentMaterializer where T : class
2020
{
21-
private readonly DbContext _dbContext;
21+
protected readonly DbContext DbContext;
2222
private readonly IResourceTypeRegistration _resourceTypeRegistration;
2323
private readonly IQueryableResourceCollectionDocumentBuilder _queryableResourceCollectionDocumentBuilder;
2424
private readonly ISingleResourceDocumentBuilder _singleResourceDocumentBuilder;
@@ -38,7 +38,7 @@ public EntityFrameworkDocumentMaterializer(
3838
ISortExpressionExtractor sortExpressionExtractor,
3939
IBaseUrlService baseUrlService)
4040
{
41-
_dbContext = dbContext;
41+
DbContext = dbContext;
4242
_resourceTypeRegistration = resourceTypeRegistration;
4343
_queryableResourceCollectionDocumentBuilder = queryableResourceCollectionDocumentBuilder;
4444
_singleResourceDocumentBuilder = singleResourceDocumentBuilder;
@@ -49,7 +49,7 @@ public EntityFrameworkDocumentMaterializer(
4949

5050
public virtual Task<IResourceCollectionDocument> GetRecords(HttpRequestMessage request, CancellationToken cancellationToken)
5151
{
52-
var query = _dbContext.Set<T>().AsQueryable();
52+
var query = DbContext.Set<T>().AsQueryable();
5353
var sortExpressions = _sortExpressionExtractor.ExtractSortExpressions(request);
5454
return _queryableResourceCollectionDocumentBuilder.BuildDocument(query, request, sortExpressions, cancellationToken);
5555
}
@@ -68,29 +68,33 @@ public virtual async Task<ISingleResourceDocument> CreateRecord(ISingleResourceD
6868
HttpRequestMessage request, CancellationToken cancellationToken)
6969
{
7070
var apiBaseUrl = GetBaseUrlFromRequest(request);
71-
var newRecord = await MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
72-
await _dbContext.SaveChangesAsync(cancellationToken);
73-
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(newRecord, apiBaseUrl, null, null);
71+
var newRecord = MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
72+
await OnCreate(newRecord);
73+
await DbContext.SaveChangesAsync(cancellationToken);
74+
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(await newRecord, apiBaseUrl, null, null);
7475

7576
return returnDocument;
7677
}
7778

79+
7880
public virtual async Task<ISingleResourceDocument> UpdateRecord(string id, ISingleResourceDocument requestDocument,
7981
HttpRequestMessage request, CancellationToken cancellationToken)
8082
{
8183
var apiBaseUrl = GetBaseUrlFromRequest(request);
82-
var newRecord = await MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
83-
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(newRecord, apiBaseUrl, null, null);
84-
await _dbContext.SaveChangesAsync(cancellationToken);
84+
var newRecord = MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
85+
await OnUpdate(newRecord);
86+
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(await newRecord, apiBaseUrl, null, null);
87+
await DbContext.SaveChangesAsync(cancellationToken);
8588

8689
return returnDocument;
8790
}
8891

8992
public virtual async Task<IJsonApiDocument> DeleteRecord(string id, HttpRequestMessage request, CancellationToken cancellationToken)
9093
{
91-
var singleResource = await _dbContext.Set<T>().FindAsync(cancellationToken, Convert.ChangeType(id, _resourceTypeRegistration.IdProperty.PropertyType));
92-
_dbContext.Set<T>().Remove(singleResource);
93-
await _dbContext.SaveChangesAsync(cancellationToken);
94+
var singleResource = DbContext.Set<T>().FindAsync(cancellationToken, Convert.ChangeType(id, _resourceTypeRegistration.IdProperty.PropertyType));
95+
await OnDelete(singleResource);
96+
DbContext.Set<T>().Remove(await singleResource);
97+
await DbContext.SaveChangesAsync(cancellationToken);
9498

9599
return null;
96100
}
@@ -107,9 +111,9 @@ protected string GetBaseUrlFromRequest(HttpRequestMessage request)
107111
/// Convert a resource object into a material record managed by EntityFramework.
108112
/// </summary>
109113
/// <returns></returns>
110-
protected virtual async Task<object> MaterializeAsync(IResourceObject resourceObject, CancellationToken cancellationToken)
114+
protected virtual async Task<T> MaterializeAsync(IResourceObject resourceObject, CancellationToken cancellationToken)
111115
{
112-
return await _entityFrameworkResourceObjectMaterializer.MaterializeResourceObject(resourceObject, cancellationToken);
116+
return (T) await _entityFrameworkResourceObjectMaterializer.MaterializeResourceObject(resourceObject, cancellationToken);
113117
}
114118

115119
/// <summary>
@@ -155,10 +159,40 @@ protected async Task<ISingleResourceDocument> GetRelatedToOne<TRelated>(string i
155159
return _singleResourceDocumentBuilder.BuildDocument(relatedResource, GetBaseUrlFromRequest(request), null, null);
156160
}
157161

162+
163+
/// <summary>
164+
/// Manipulate entity before create.
165+
/// </summary>
166+
/// <param name="record"></param>
167+
/// <returns></returns>
168+
protected virtual async Task OnCreate(Task<T> record)
169+
{
170+
await record;
171+
}
172+
173+
/// <summary>
174+
/// Manipulate entity before update.
175+
/// </summary>
176+
/// <param name="record"></param>
177+
protected virtual async Task OnUpdate(Task<T> record)
178+
{
179+
await record;
180+
}
181+
182+
/// <summary>
183+
/// Manipulate entity before delete.
184+
/// </summary>
185+
/// <param name="record"></param>
186+
/// <returns></returns>
187+
protected virtual async Task OnDelete(Task<T> record)
188+
{
189+
await record;
190+
}
191+
158192
private IQueryable<TResource> Filter<TResource>(Expression<Func<TResource, bool>> predicate,
159193
params Expression<Func<TResource, object>>[] includes) where TResource : class
160194
{
161-
IQueryable<TResource> query = _dbContext.Set<TResource>();
195+
IQueryable<TResource> query = DbContext.Set<TResource>();
162196
if (includes != null && includes.Any())
163197
query = includes.Aggregate(query, (current, include) => current.Include(include));
164198

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,42 @@ The classes in the `JSONAPI.EntityFramework` namespace take great advantage of t
7171

7272
- [ ] Add some hints about the configuration of JSONAPI.EntityFramework
7373

74+
## Manipulate entities before JSONAPI.EntityFramework persists them
75+
To change your entities before they get persisted you can extend the `EntityFrameworkDocumentMaterializer<T>` class. You need to register your custom DocumentMaterializer in your `JsonApiConfiguration` like that:
76+
```C#
77+
configuration.RegisterEntityFrameworkResourceType<MyEntityType>(c =>c.UseDocumentMaterializer<CustomDocumentMaterializer>());
78+
```
79+
Afterwards you can override the `OnCreate`, `OnUpdate` or `OnDelete` methods in your `CustomDocumentMaterializer`.
80+
81+
```C#
82+
protected override async Task OnCreate(Task<MyEntityType> record)
83+
{
84+
await base.OnUpdate(record);
85+
var entity = await record;
86+
entity.CreatedOn = DateTime.Now;
87+
entity.CreatedBy = Principal?.Identity;
88+
}
89+
```
90+
91+
> :information_source: HINT: To get the `Principal` you can add the following part into your `Startup.cs` which registers the `Principal` in Autofac and define a constructor Parameter on your `CustomDocumentMaterializer` of type `IPrincipal`.
92+
93+
```C#
94+
95+
configurator.OnApplicationLifetimeScopeCreating(builder =>
96+
{
97+
// ...
98+
builder.Register(ctx => HttpContext.Current.GetOwinContext()).As<IOwinContext>();
99+
builder.Register((c, p) =>
100+
{
101+
var owin = c.Resolve<IOwinContext>();
102+
return owin.Authentication.User;
103+
})
104+
.As<IPrincipal>()
105+
.InstancePerRequest();
106+
}
107+
```
108+
109+
74110
## Set the context path of JSONAPI.EntityFramework
75111

76112
Per default the routes created for the registered models from EntityFramework will appear in root folder. This can conflict with folders of the file system or other routes you may want to serve from the same project.

0 commit comments

Comments
 (0)