Sample: Copying an Activity Between Tenants

In multi-tenant applications, it is often necessary to move or copy data between tenants. Bloqs supports this through the IDataServiceCreator, which lets you create DataService instances for different tenants and perform cross-tenant operations.


Operation: Copy Activity

The Copy Activity operation demonstrates how to copy an Activity entity from a source tenant to a destination tenant.


Operation Model

The operation is fueled by an input model that specifies the tenant and activity to copy:

public class CopyActivityInput : Entity 
{
    public required Guid TenantId { get; set; }
    public required Guid ActivityId { get; set; }
}
  • TenantId: The target tenant where the activity should be copied.

  • ActivityId: The source activity to copy.


Operation Handler (API)

[OperationCommandHandler(AppConstants.Operations.CopyActivityName)]
public class CopyActivity(IDataServiceCreator dataServiceCreator)
    : ICommandHandler<OperationCommand, OperationCommandResult>
{
    public async Task<OperationCommandResult> HandleAsync(
        OperationCommand command,
        CancellationToken cancellationToken = default
    )
    {
        try
        {
            var input =
                command.InputEntity as CopyActivityInput
                ?? throw new Exception("Invalid input entity for CopyActivity operation");

            // Get the destination tenant
            var destinationTenant = await dataServiceCreator
                .GetDataService<Tenant>(DataServiceCreatorArgs.Create<Tenant>(command.AppName))
                .GetAsync(input.TenantId);

            // Source data service (current tenant)
            var sourceDataService = dataServiceCreator.GetDataService<Activity>(
                DataServiceCreatorArgs.FromCommand<Activity>(command)
            );

            // Destination data service (target tenant)
            var destinationDataService = dataServiceCreator.GetDataService<Activity>(
                DataServiceCreatorArgs.Create<Activity>(command.AppName, destinationTenant.Name)
            );

            // Fetch the source activity
            var sourceActivity = await sourceDataService.GetAsync(input.ActivityId);

            // Assign a new ID for the destination copy
            sourceActivity.Id = Guid.CreateVersion7();

            // Save the copied activity into the destination tenant
            await destinationDataService.SaveAsync([sourceActivity], true);

            return OperationCommandResult.CreateSuccess(null);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error copying activity: {ex.Message}");
            return OperationCommandResult.CreateFailure();
        }
    }
}

Page Definition

To expose the Copy Activity operation to end users, a page is defined in the app model. The page contains an OperationStartComponent that references the operation and allows the user to select both the Activity and the Tenant.

appBuilder.AddPage(
    new Page
    {
        Id = AppConstants.Pages.CopyActivityOperationPage,
        Title = "Copy Activity",
        Name = "copy-activity",
        Area = ContainerComponent.Create(
            new OperationStartComponent
            {
                OperationId = IdGen.GetOperationId("CopyActivity"),
                EntityClassId = IdGen.GetEntityClassId<CopyActivityInput>(),
                Area = PageBuilderExtensions.VerticalContainer(
                    new EntityRelationInputComponent
                    {
                        EntityClassId = IdGen.GetEntityClassId<Activity>(),
                        PropertyId = IdGen.GetPropertyId<CopyActivityInput>(
                            nameof(CopyActivityInput.ActivityId)
                        ),
                        IsRequired = true,
                    },
                    new EntityRelationInputComponent
                    {
                        EntityClassId = IdGen.GetEntityClassId<Tenant>(),
                        PropertyId = IdGen.GetPropertyId<CopyActivityInput>(
                            nameof(CopyActivityInput.TenantId)
                        ),
                        IsRequired = true,
                    }
                ),
                AfterCancelPageId = AppConstants.Pages.HomePage,
                AfterExecutePageId = AppConstants.Pages.HomePage,
            }
        ),
    }
)
.AddOperation(
    new Operation
    {
        Id = IdGen.GetOperationId("CopyActivity"),
        Name = "CopyActivity",
    }
);
  • OperationId links the page to the CopyActivity operation.

  • EntityClassId references the CopyActivityInput model.

  • EntityRelationInputComponent lets the user pick an Activity and a Tenant.

  • AfterCancelPageId / AfterExecutePageId define navigation flow after the operation.


Key Concepts

  1. Operation Input Model

    • Defines what data is required for the operation (TenantId, ActivityId).

  2. Operation Handler

    • Performs the actual logic of fetching the entity from one tenant and saving it into another.

  3. Page Modeling

    • Defines how the operation is exposed to the end user.

    • Uses Bloqs components (OperationStartComponent, EntityRelationInputComponent) to build the UI.


Summary

  • Multi-tenant data exchange is modeled as an Operation in Bloqs.

  • An input model (CopyActivityInput) defines the required parameters.

  • An operation handler performs the cross-tenant copy logic.

  • A page definition exposes the operation to users, allowing them to select an activity and a destination tenant.

Last updated