This document demonstrates the complete Delete flow using the DeleteProduct feature from the EBusiness application. The flow starts with a DELETE request to the controller and ends with event publishing and subscriber processing.
Side Effects → Inventory updates, supplier notifications, analytics
Key Benefits
Data Safety: Soft delete preserves data for recovery
Business Rules: Validates deletion is allowed
Audit Trail: Tracks who deleted what and when
Event-Driven: Notifies other systems of deletions
Testable: Each component can be tested independently
Maintainable: Clear separation of concerns
This DeleteProduct example demonstrates how FlexBase enables clean, maintainable, and scalable delete operations with proper validation, soft delete patterns, and event-driven architecture! 🚀
public async Task<CommandResult> DeleteProduct(DeleteProductDto dto)
{
var packet = await ProcessBusinessRuleSequence<DeleteProductDataPacket, DeleteProductSequence, DeleteProductDto, FlexAppContextBridge>(dto);
if (packet.HasError)
{
return new CommandResult(Status.Failed, packet.Errors());
}
else
{
dto.SetGeneratedId(_pkGenerator.GenerateKey());
DeleteProductCommand cmd = new DeleteProductCommand
{
Dto = dto,
};
await ProcessCommand(cmd);
CommandResult cmdResult = new CommandResult(Status.Success);
YourOutputResultModel outputResult = new YourOutputResultModel();
cmdResult.result = outputResult;
return cmdResult;
}
}
public class DeleteProductSequence : FlexiBusinessRuleSequenceBase<DeleteProductDataPacket>
{
public DeleteProductSequence()
{
this.Add<IsValidProduct>();
}
}
public partial class DeleteProductDataPacket : FlexiFlowDataPacketWithDtoBridge<DeleteProductDto, FlexAppContextBridge>
{
protected readonly ILogger<DeleteProductDataPacket> _logger;
public DeleteProductDataPacket(ILogger<DeleteProductDataPacket> logger)
{
_logger = logger;
}
#region "Properties
//Models and other properties goes here
#endregion
}
public partial class IsValidProduct : FlexiBusinessRuleBase, IFlexiBusinessRule<DeleteProductDataPacket>
{
public override string Id { get; set; } = "3a1cd5b1fa98b4c63378de9607706082";
public override string FriendlyName { get; set; } = "IsValidProduct";
protected readonly ILogger<IsValidProduct> _logger;
protected readonly RepoFactory _repoFactory;
public IsValidProduct(ILogger<IsValidProduct> logger, RepoFactory repoFactory)
{
_logger = logger;
_repoFactory = repoFactory;
}
public virtual async Task Validate(DeleteProductDataPacket packet)
{
//Uncomment the below line if validating against a db data using your repo
//_repoFactory.Init(packet.Dto);
//If any validation fails, uncomment and use the below line of code to add error to the packet
//packet.AddError("key", "ErrorMessage");
await Task.CompletedTask; //If you have any await in the validation, remove this line
}
}
public virtual async Task Execute(DeleteProductCommand cmd, IFlexServiceBusContext serviceBusContext)
{
_flexAppContext = cmd.Dto.GetAppContext();
_repoFactory.Init(cmd.Dto);
_model = _flexHost.GetDomainModel<Product>().DeleteProduct(cmd);
if (_model != null)
{
_repoFactory.GetRepo().InsertOrUpdate(_model);
int records = await _repoFactory.GetRepo().SaveAsync();
if (records > 0)
{
_logger.LogDebug("{} with {} deleted from Database: ", typeof(Product).Name, _model.Id);
}
else
{
_logger.LogWarning("No records deleted for {} with {}", typeof(Product).Name, _model.Id);
}
//EventCondition = CONDITION_ONSUCCESS;
}
else
{
//you may raise an event here to notify about the error
//EventCondition = CONDITION_ONFAILED;
}
await this.Fire(EventCondition, serviceBusContext);
}
public virtual Product DeleteProduct(DeleteProductCommand cmd)
{
Guard.AgainstNull("Product model cannot be empty", cmd);
this.Id = cmd.Dto.Id;
this.SetDeleted();
//Set your appropriate SetDeleted for the inner object here
return this;
}
public class DeleteProductNsbHandler : NsbCommandHandler<DeleteProductCommand>
{
readonly ILogger<DeleteProductNsbHandler> _logger;
readonly IFlexHost _flexHost;
readonly IDeleteProductHandler _handler;
public DeleteProductNsbHandler(ILogger<DeleteProductNsbHandler> logger, IFlexHost flexHost, IDeleteProductHandler handler)
{
_logger = logger;
_flexHost = flexHost;
_handler = handler;
}
public override async Task Handle(DeleteProductCommand message, IMessageHandlerContext context)
{
_logger.LogTrace($"Executing {nameof(DeleteProductNsbHandler)}");
await _handler.Execute(message, new NsbHandlerContextBridge(context));
}
}
public class ProductDeletedEvent : FlexEventBridge<FlexAppContextBridge>
{
// Event data is automatically populated by FlexBase
}
public partial class NotifyInventoryOnProductDeleted : INotifyInventoryOnProductDeleted
{
protected readonly ILogger<NotifyInventoryOnProductDeleted> _logger;
protected string EventCondition = "";
public NotifyInventoryOnProductDeleted(ILogger<NotifyInventoryOnProductDeleted> logger)
{
_logger = logger;
}
public virtual async Task Execute(ProductDeletedEvent @event, IFlexServiceBusContext serviceBusContext)
{
_flexAppContext = @event.AppContext;
//TODO: Write your business logic here:
// - Update inventory levels
// - Notify suppliers
// - Update analytics
// - Archive related data
await this.Fire<NotifyInventoryOnProductDeleted>(EventCondition, serviceBusContext);
}
}
public partial class DeleteProductDto : DtoBridge
{
public string Id { get; set; }
}
public class DeleteProductCommand : FlexCommandBridge<DeleteProductDto, FlexAppContextBridge>
{
// Command data is automatically populated by FlexBase
}