Skip to content

Question: which layer to implement audit fields #637

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

Closed
fdlane opened this issue Nov 27, 2019 · 3 comments
Closed

Question: which layer to implement audit fields #637

fdlane opened this issue Nov 27, 2019 · 3 comments

Comments

@fdlane
Copy link
Contributor

fdlane commented Nov 27, 2019

Description

Currently I am implementing audit fields in the service layer. However, the values are only persisted to the db if the api user includes the properties in the request.

entity.EditedBy = _currentUser.Username; is not applied if the user does not include the property in the Patch request.

How do I get the audit fields to always be persisted?

public BaseService(
  IJsonApiContext jsonApiContext,
  IEntityRepository<T, Guid> repository,
  ILoggerFactory loggerFactory,
  ICurrentUser currentUser) : base(jsonApiContext, repository, loggerFactory)
{
  _currentUser = currentUser;
}

public override async Task<T> CreateAsync(T entity)
{
  entity.CreatedBy = _currentUser.Username;
  var newEntity = await base.CreateAsync(entity);
  return newEntity;
}

public override async Task<T> UpdateAsync(Guid id, T entity)
{
  entity.EditedBy = _currentUser.Username;
  entity.EditedDate = DateTime.Now;

  var updatedEntity = await base.UpdateAsync(id, entity);

  return updatedEntity;
}

Environment

  • JsonApiDotNetCore Version: v4.0.0-alpha3
@maurei
Copy link
Member

maurei commented Nov 28, 2019

Hi @fdlane, this indeed is a bit less easy than one would expect. Internally the repository accesses a list of all targeted attributes to know which ones to update:

foreach (var attribute in _targetedFields.Attributes)

That means AttrAttribute instances associated to your EditedBy and EditedDate field must be in that list. Indeed, this is taken care of under the hood when an attribute is targeted through a PATCH request. In your case you would have to add this attribute to the list manually.

In v4.0.0-alpha4 this is relatively straight forward using the ITargetedFields and IResourceGraphExplorer services:

public BaseService(
  ... ,
  ITargetedFields targetedFields,
  IResourceGraphExplorer graph,
  ...) : base( ... ) { ... }

public override async Task<T> UpdateAsync(Guid id, T entity)
{
  entity.EditedBy = _currentUser.Username;
  entity.EditedDate = DateTime.Now;

  var attributes = _graph.GetAttributes<YourResource>( r => new { r.EditedBy, r.EditedDate });
  _targetedFields.AddRange(attributes);

  var updatedEntity = await base.UpdateAsync(id, entity);

  return updatedEntity;
}

I see you're on alpha3 though. There, the equivalent of ITargetedFields is the AttributesToUpdate property on IJsonApiContext:

foreach (var attr in _jsonApiContext.AttributesToUpdate.Keys)

To get a hold of the attributes see my post here #536 (comment). That is still a bit complicated in alpha3. I would recommend you to switch to alpha4 ASAP anyway.

Let me know if that helps!

@fdlane
Copy link
Contributor Author

fdlane commented Nov 29, 2019

This was a great help and we will be moving to alpha4 soon. Thank you!

My base class based on #536

  public class BaseService<T> : EntityResourceService<T, Guid> where T : BaseEntity
    {
      private readonly ICurrentUser _currentUser;
      private readonly IResourceGraph _resourceGraph;
      private readonly IJsonApiContext _jsonApiContext;

      public BaseService(
        IJsonApiContext jsonApiContext,
        IEntityRepository<T, Guid> repository,
        ILoggerFactory loggerFactory,
        IResourceGraph resourceGraph,
        ICurrentUser currentUser) : base(jsonApiContext, repository, loggerFactory)
      {
        _currentUser = currentUser;
        _resourceGraph = resourceGraph;
        _jsonApiContext = jsonApiContext;
      }

      public override async Task<T> CreateAsync(T entity)
      {
        entity.CreatedBy = _currentUser.Username;
        var newEntity = await base.CreateAsync(entity);
        return newEntity;
      }

      public override async Task<T> UpdateAsync(Guid id, T entity)
      {
        entity.EditedBy = _currentUser.Username;
        entity.EditedDate = DateTime.Now;

        var editedByAttribute = _resourceGraph.GetContextEntity(typeof(T)).Attributes.Single(a => a.InternalAttributeName == "EditedBy");
        _jsonApiContext.AttributesToUpdate.Add(editedByAttribute, editedByAttribute);

        var editedDateAttribute = _resourceGraph.GetContextEntity(typeof(T)).Attributes.Single(a => a.InternalAttributeName == "EditedDate");
        _jsonApiContext.AttributesToUpdate.Add(editedDateAttribute, editedDateAttribute);

        var updatedEntity = await base.UpdateAsync(id, entity);

        return updatedEntity;
      }
    }

@fdlane fdlane closed this as completed Nov 29, 2019
@fdlane
Copy link
Contributor Author

fdlane commented Nov 29, 2019

Thanks for the help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants