diff --git a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs index d7b3ce73..0b86166f 100644 --- a/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs +++ b/JSONAPI.EntityFramework/Http/EntityFrameworkDocumentMaterializer.cs @@ -18,7 +18,7 @@ namespace JSONAPI.EntityFramework.Http /// public class EntityFrameworkDocumentMaterializer : IDocumentMaterializer where T : class { - private readonly DbContext _dbContext; + protected readonly DbContext DbContext; private readonly IResourceTypeRegistration _resourceTypeRegistration; private readonly IQueryableResourceCollectionDocumentBuilder _queryableResourceCollectionDocumentBuilder; private readonly ISingleResourceDocumentBuilder _singleResourceDocumentBuilder; @@ -38,7 +38,7 @@ public EntityFrameworkDocumentMaterializer( ISortExpressionExtractor sortExpressionExtractor, IBaseUrlService baseUrlService) { - _dbContext = dbContext; + DbContext = dbContext; _resourceTypeRegistration = resourceTypeRegistration; _queryableResourceCollectionDocumentBuilder = queryableResourceCollectionDocumentBuilder; _singleResourceDocumentBuilder = singleResourceDocumentBuilder; @@ -49,7 +49,7 @@ public EntityFrameworkDocumentMaterializer( public virtual Task GetRecords(HttpRequestMessage request, CancellationToken cancellationToken) { - var query = _dbContext.Set().AsQueryable(); + var query = DbContext.Set().AsQueryable(); var sortExpressions = _sortExpressionExtractor.ExtractSortExpressions(request); return _queryableResourceCollectionDocumentBuilder.BuildDocument(query, request, sortExpressions, cancellationToken); } @@ -68,29 +68,33 @@ public virtual async Task 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 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 DeleteRecord(string id, HttpRequestMessage request, CancellationToken cancellationToken) { - var singleResource = await _dbContext.Set().FindAsync(cancellationToken, Convert.ChangeType(id, _resourceTypeRegistration.IdProperty.PropertyType)); - _dbContext.Set().Remove(singleResource); - await _dbContext.SaveChangesAsync(cancellationToken); + var singleResource = DbContext.Set().FindAsync(cancellationToken, Convert.ChangeType(id, _resourceTypeRegistration.IdProperty.PropertyType)); + await OnDelete(singleResource); + DbContext.Set().Remove(await singleResource); + await DbContext.SaveChangesAsync(cancellationToken); return null; } @@ -107,9 +111,9 @@ protected string GetBaseUrlFromRequest(HttpRequestMessage request) /// Convert a resource object into a material record managed by EntityFramework. /// /// - protected virtual async Task MaterializeAsync(IResourceObject resourceObject, CancellationToken cancellationToken) + protected virtual async Task MaterializeAsync(IResourceObject resourceObject, CancellationToken cancellationToken) { - return await _entityFrameworkResourceObjectMaterializer.MaterializeResourceObject(resourceObject, cancellationToken); + return (T) await _entityFrameworkResourceObjectMaterializer.MaterializeResourceObject(resourceObject, cancellationToken); } /// @@ -155,10 +159,40 @@ protected async Task GetRelatedToOne(string i return _singleResourceDocumentBuilder.BuildDocument(relatedResource, GetBaseUrlFromRequest(request), null, null); } + + /// + /// Manipulate entity before create. + /// + /// + /// + protected virtual async Task OnCreate(Task record) + { + await record; + } + + /// + /// Manipulate entity before update. + /// + /// + protected virtual async Task OnUpdate(Task record) + { + await record; + } + + /// + /// Manipulate entity before delete. + /// + /// + /// + protected virtual async Task OnDelete(Task record) + { + await record; + } + private IQueryable Filter(Expression> predicate, params Expression>[] includes) where TResource : class { - IQueryable query = _dbContext.Set(); + IQueryable query = DbContext.Set(); if (includes != null && includes.Any()) query = includes.Aggregate(query, (current, include) => current.Include(include)); diff --git a/README.md b/README.md index b930f0e6..129060b7 100644 --- a/README.md +++ b/README.md @@ -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` class. You need to register your custom DocumentMaterializer in your `JsonApiConfiguration` like that: +```C# +configuration.RegisterEntityFrameworkResourceType(c =>c.UseDocumentMaterializer()); +``` +Afterwards you can override the `OnCreate`, `OnUpdate` or `OnDelete` methods in your `CustomDocumentMaterializer`. + +```C# +protected override async Task OnCreate(Task 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(); +builder.Register((c, p) => + { + var owin = c.Resolve(); + return owin.Authentication.User; + }) + .As() + .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.