Service Logic

Service Logic executes on create, read, update or delete of an entity. Service Logic classes are automatically discovered and executed by the framework.

Logic Stage

Service Logic can execute at the listed stages below.

  • PreValidation: Before security validation and before the save to the database
  • PreOperation: After security validation and before the save to the database
  • PostOperation: After the save to the database

Create Service Logic

To create Service Logic, create a new file in the project in the Logic folder.

Project / Logic / MyEntityService.cs

// The ServiceLogic attribute specifies what table this will execute on, what operation(s), and when (before or after the transaction)
[ServiceLogic(nameof(MyEntity), DataOperation.Create | DataOperation.Update, LogicStage.PreOperation, 100)]
public class MyEntityService : IServiceLogic
{
public async Task<Response<object?>> Execute(ServiceContext context)
{
if (context.DataOperation is DataOperation.Create)
{
// Do something on Create
}
if (context.DataOperation is DataOperation.Update)
{
// Do something on Update
}
return ServiceResult.Success();
}
}

The ServiceLogic attribute takes the following parameters.

  • TableName: The name of the entity the service logic will execute on. '*' for all entities.
  • DataOperations: Flags that determine what data operations this should execute on, create, read, update, and delete.
  • LogicStages: Flags that determine which stages the service logic should exeucte on, PreOperation, or PostOperation.
  • Order: Service Logic classes executing on the same table, data operation, and logic stage, will execute following the order.

ServiceContext

ServiceContext has a number of properties that can be used during execution.

Project / Logic / MyEntityService.cs

[ServiceLogic(nameof(MyEntity), DataOperation.Create | DataOperation.Update | DataOperation.Read, LogicStage.PreOperation | LogicStage.PostOperation, 100)]
public class MyEntityService : IServiceLogic
{
public async Task<Response<object?>> Execute(ServiceContext context)
{
// Get the db context
DataContext db = context.GetDbContext<DataContext>();
// Do something on Create or Update
if (context.DataOperation is DataOperation.Create or DataOperation.Update)
{
// If this is an update, PreEntity is populated with the entity before the update
MyEntity preEntity = context.GetPreEntity<MyEntity>();
// The Entity property will have the latest values to be saved to the database
MyEntity entity = context.GetEntity<MyEntity>()
// Do something before the SaveChanges call
if (context.LogicStage is LogicStage.PreOperation)
{
// If the Name field is being updated (always true on Create)
if (context.ValueChanged(nameof(MyEntity.Name)))
{
}
}
// Do something after the SaveChanges call
// On create if we need the id of the record being saved
if (context.LogicStage is LogicStage.PostOperation)
{
}
}
// Do something on Read
if (context.DataOperation is DataOperation.Read)
{
// Get the query that was sent to the api
ReadInput readInput = context.ReadInput;
}
return ServiceResult.Success();
}
}

Modify Records

Use the Create, Update, and Delete methods of the ServiceContext to modify records. While it is possible to modify records directly using the Entity Framework DbContext, using the ServiceContext ensures that the service logic is executed.

Project / Logic / MyEntityService.cs

[ServiceLogic(nameof(MyEntity), DataOperation.Create | DataOperation.Update, LogicStage.PreOperation, 100)]
public class MyEntityService : IServiceLogic
{
public async Task<Response<object?>> Execute(ServiceContext context)
{
// Get the db context
DataContext db = context.GetDbContext<DataContext>();
List<Widget> widgets = await db.Widgets.ToListAsync();
// Update the price of all Widgets
foreach (Widget widget in widgets)
{
widget.Price = 9.99M;
// Update the Widget and trigger any service logic
await context.Update<Widget>(widget);
}
return ServiceResult.Success();
}
}

Dependency Injection

Service Logic classes support constructor-based dependency injection. The framework uses ActivatorUtilities.CreateInstance() to instantiate service logic classes, allowing automatic injection of registered services:

Project / Logic / WidgetService.cs

[ServiceLogic(nameof(Widget), DataOperation.Create, LogicStage.PreOperation)]
public class WidgetService : IServiceLogic
{
private readonly IEmailService _emailService;
private readonly ICacheService _cacheService;
public WidgetService(IEmailService emailService, ICacheService cacheService)
{
_emailService = emailService;
_cacheService = cacheService;
}
public async Task<Response<object?>> Execute(ServiceContext context)
{
// Use injected services
await _emailService.SendNotification("Widget created");
await _cacheService.InvalidateCache("widgets");
// Use ServiceContext for framework services
context.Logger.LogInformation("Widget processing completed");
return ServiceResult.Success();
}
}

Key Benefits

  • Standard .NET constructor-based dependency injection
  • Clean separation of concerns and testable code
  • Access to all services registered in the DI container
  • Framework automatically resolves dependencies

Available Services

  • Constructor injection: Custom business services and third-party services registered in DI container
  • ServiceContext: Framework services like context.Logger, context.GetDbContext<T>(), context.ExecutingUserId, etc.

Performance

For optimal performance, prioritize using PreOperation logic whenever possible. This allows the database save to be delayed until all records are processed, or until an entity with PostOperation logic is triggered.

Was this page helpful?