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
64 changes: 49 additions & 15 deletions JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace JSONAPI.EntityFramework.Http
/// </summary>
public class EntityFrameworkDocumentMaterializer<T> : IDocumentMaterializer where T : class
{
private readonly DbContext _dbContext;
protected readonly DbContext DbContext;
private readonly IResourceTypeRegistration _resourceTypeRegistration;
private readonly IQueryableResourceCollectionDocumentBuilder _queryableResourceCollectionDocumentBuilder;
private readonly ISingleResourceDocumentBuilder _singleResourceDocumentBuilder;
Expand All @@ -38,7 +38,7 @@ public EntityFrameworkDocumentMaterializer(
ISortExpressionExtractor sortExpressionExtractor,
IBaseUrlService baseUrlService)
{
_dbContext = dbContext;
DbContext = dbContext;
_resourceTypeRegistration = resourceTypeRegistration;
_queryableResourceCollectionDocumentBuilder = queryableResourceCollectionDocumentBuilder;
_singleResourceDocumentBuilder = singleResourceDocumentBuilder;
Expand All @@ -49,7 +49,7 @@ public EntityFrameworkDocumentMaterializer(

public virtual Task<IResourceCollectionDocument> GetRecords(HttpRequestMessage request, CancellationToken cancellationToken)
{
var query = _dbContext.Set<T>().AsQueryable();
var query = DbContext.Set<T>().AsQueryable();
var sortExpressions = _sortExpressionExtractor.ExtractSortExpressions(request);
return _queryableResourceCollectionDocumentBuilder.BuildDocument(query, request, sortExpressions, cancellationToken);
}
Expand All @@ -68,29 +68,33 @@ public virtual async Task<ISingleResourceDocument> CreateRecord(ISingleResourceD
HttpRequestMessage request, CancellationToken cancellationToken)
{
var apiBaseUrl = GetBaseUrlFromRequest(request);
var newRecord = await MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
await _dbContext.SaveChangesAsync(cancellationToken);
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(newRecord, apiBaseUrl, null, null);
var newRecord = MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
await OnCreate(newRecord);
await DbContext.SaveChangesAsync(cancellationToken);
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(await newRecord, apiBaseUrl, null, null);

return returnDocument;
}


public virtual async Task<ISingleResourceDocument> UpdateRecord(string id, ISingleResourceDocument requestDocument,
HttpRequestMessage request, CancellationToken cancellationToken)
{
var apiBaseUrl = GetBaseUrlFromRequest(request);
var newRecord = await MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(newRecord, apiBaseUrl, null, null);
await _dbContext.SaveChangesAsync(cancellationToken);
var newRecord = MaterializeAsync(requestDocument.PrimaryData, cancellationToken);
await OnUpdate(newRecord);
var returnDocument = _singleResourceDocumentBuilder.BuildDocument(await newRecord, apiBaseUrl, null, null);
await DbContext.SaveChangesAsync(cancellationToken);

return returnDocument;
}

public virtual async Task<IJsonApiDocument> DeleteRecord(string id, HttpRequestMessage request, CancellationToken cancellationToken)
{
var singleResource = await _dbContext.Set<T>().FindAsync(cancellationToken, Convert.ChangeType(id, _resourceTypeRegistration.IdProperty.PropertyType));
_dbContext.Set<T>().Remove(singleResource);
await _dbContext.SaveChangesAsync(cancellationToken);
var singleResource = DbContext.Set<T>().FindAsync(cancellationToken, Convert.ChangeType(id, _resourceTypeRegistration.IdProperty.PropertyType));
await OnDelete(singleResource);
DbContext.Set<T>().Remove(await singleResource);
await DbContext.SaveChangesAsync(cancellationToken);

return null;
}
Expand All @@ -107,9 +111,9 @@ protected string GetBaseUrlFromRequest(HttpRequestMessage request)
/// Convert a resource object into a material record managed by EntityFramework.
/// </summary>
/// <returns></returns>
protected virtual async Task<object> MaterializeAsync(IResourceObject resourceObject, CancellationToken cancellationToken)
protected virtual async Task<T> MaterializeAsync(IResourceObject resourceObject, CancellationToken cancellationToken)
{
return await _entityFrameworkResourceObjectMaterializer.MaterializeResourceObject(resourceObject, cancellationToken);
return (T) await _entityFrameworkResourceObjectMaterializer.MaterializeResourceObject(resourceObject, cancellationToken);
}

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


/// <summary>
/// Manipulate entity before create.
/// </summary>
/// <param name="record"></param>
/// <returns></returns>
protected virtual async Task OnCreate(Task<T> record)
{
await record;
}

/// <summary>
/// Manipulate entity before update.
/// </summary>
/// <param name="record"></param>
protected virtual async Task OnUpdate(Task<T> record)
{
await record;
}

/// <summary>
/// Manipulate entity before delete.
/// </summary>
/// <param name="record"></param>
/// <returns></returns>
protected virtual async Task OnDelete(Task<T> record)
{
await record;
}

private IQueryable<TResource> Filter<TResource>(Expression<Func<TResource, bool>> predicate,
params Expression<Func<TResource, object>>[] includes) where TResource : class
{
IQueryable<TResource> query = _dbContext.Set<TResource>();
IQueryable<TResource> query = DbContext.Set<TResource>();
if (includes != null && includes.Any())
query = includes.Aggregate(query, (current, include) => current.Include(include));

Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,42 @@ The classes in the `JSONAPI.EntityFramework` namespace take great advantage of t

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

## Manipulate entities before JSONAPI.EntityFramework persists them
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:
```C#
configuration.RegisterEntityFrameworkResourceType<MyEntityType>(c =>c.UseDocumentMaterializer<CustomDocumentMaterializer>());
```
Afterwards you can override the `OnCreate`, `OnUpdate` or `OnDelete` methods in your `CustomDocumentMaterializer`.

```C#
protected override async Task OnCreate(Task<MyEntityType> record)
{
await base.OnUpdate(record);
var entity = await record;
entity.CreatedOn = DateTime.Now;
entity.CreatedBy = Principal?.Identity;
}
```

> :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`.

```C#

configurator.OnApplicationLifetimeScopeCreating(builder =>
{
// ...
builder.Register(ctx => HttpContext.Current.GetOwinContext()).As<IOwinContext>();
builder.Register((c, p) =>
{
var owin = c.Resolve<IOwinContext>();
return owin.Authentication.User;
})
.As<IPrincipal>()
.InstancePerRequest();
}
```


## Set the context path of JSONAPI.EntityFramework

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.
Expand Down