App Studio
Audit Trail
Every write operation through the AIExtension service layer automatically creates an audit log entry. Controllers do not need to implement audit logic. The audit trail records who changed what, when, and from which operation — providing a complete history of all app definition changes.
Audit Log Table
-- AIExt_AppAuditLog
CREATE TABLE AIExt_AppAuditLog (
Id BIGINT PRIMARY KEY,
TenantId NVARCHAR(100) NOT NULL,
AppId NVARCHAR(100) NOT NULL,
EntityType NVARCHAR(100) NOT NULL, -- "App", "Page", "Widget", "Layout"
EntityId NVARCHAR(200) NOT NULL, -- e.g., "lead-detail" for a page
Action NVARCHAR(100) NOT NULL, -- "Save", "Delete", "Import", "Export"
ChangedBy NVARCHAR(100) NOT NULL, -- userId from JWT
ChangedAt DATETIME2 NOT NULL,
ClientIp NVARCHAR(50), -- IP from request headers
DiffJson NVARCHAR(MAX), -- JSON diff (before/after) for saves
Notes NVARCHAR(500) -- Optional: import source version, etc.
);
What Is Automatically Audited
| Operation | Entity | Action logged |
|---|---|---|
| SaveAppAsync | App | "Save" with JSON diff of the app definition |
| DeleteAppAsync | App | "Delete" |
| SavePageAsync | Page | "Save" with JSON diff of the page config |
| DeletePageAsync | Page | "Delete" |
| SaveLayoutAsync | Layout | "Save" for each page's widget set |
| ImportAppAsync | App | "Import" with bundle version label in Notes |
| ExportAppAsync | App | "Export" with requested by userId |
Audit in the Service Layer — Automatic for All Writes
// The audit writer is called inside every write method:
private async Task WriteAuditLogAsync(
string tenantId, string appId, string entityType,
string entityId, string action, string changedBy,
string? diffJson = null, string? notes = null)
{
var entry = new AppAuditLogEntity
{
TenantId = tenantId,
AppId = appId,
EntityType = entityType,
EntityId = entityId,
Action = action,
ChangedBy = changedBy,
ChangedAt = DateTime.UtcNow,
ClientIp = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString(),
DiffJson = diffJson,
Notes = notes
};
_db.AppAuditLogs.Add(entry);
await _db.SaveChangesAsync();
}
Reading the Audit Log via the Service API
// IAppStudioService
Task> GetAuditLogAsync(string tenantId, string appId, AuditQuery query);
// AuditQuery supports filtering:
var query = new AuditQuery
{
From = DateTime.UtcNow.AddDays(-30),
To = DateTime.UtcNow,
Action = "Save", // optional filter by action
ChangedBy = "user-abc" // optional filter by user
};
var log = await _service.GetAuditLogAsync(tenantId, appId, query);
Viewing the Audit Log in the Designer
App designers can view the audit log for an app directly in the App Studio Designer:
1
Open the app in App Studio Designer
2
Click the Audit Log icon in the left toolbar (clock icon)
3
Browse the chronological list of changes — who changed what, when
4
Click any "Save" entry to view the JSON diff (before/after comparison)
5
Filter by date range, user, or action type using the filter controls
Audit is non-bypassable
Because all data access goes through
IAppStudioService, audit logging is guaranteed for every write. It is structurally impossible for a controller to write to an app definition without creating an audit entry — the service method always calls the audit writer before returning.