App Studio
AIExtension.Service API
The IAppStudioService interface is the complete contract between App Studio controllers and the AIExtension data layer. Every data operation has an explicit method on this interface — there is no other way to access App Studio data.
IAppStudioService Interface
// AppStudio/Services/IAppStudioService.cs
public interface IAppStudioService
{
// App definitions
Task GetAppAsync(string tenantId, string appId);
Task> GetAppsAsync(string tenantId);
Task SaveAppAsync(string tenantId, AppDefinition app, string changedByUserId);
Task DeleteAppAsync(string tenantId, string appId, string deletedByUserId);
// Pages
Task> GetPagesAsync(string tenantId, string appId);
Task GetPageAsync(string tenantId, string appId, string pageId);
Task SavePageAsync(string tenantId, string appId, AppPage page, string changedByUserId);
Task DeletePageAsync(string tenantId, string appId, string pageId, string deletedByUserId);
// Widgets and layout
Task> GetWidgetsAsync(string tenantId, string appId, string pageId);
Task SaveLayoutAsync(string tenantId, string appId, string pageId, IList placements, string changedByUserId);
// Export / Import
Task ExportAppAsync(string tenantId, string appId, string exportedByUserId);
Task ImportAppAsync(string tenantId, AppExportBundle bundle, ImportOptions options, string importedByUserId);
// Audit
Task> GetAuditLogAsync(string tenantId, string appId, AuditQuery query);
}
Key Methods Explained
GetAppAsync
// Returns the full AppDefinition (including pages, navigation, variables, permissions)
// Served from Redis cache if available — fast path for app loads
Task GetAppAsync(string tenantId, string appId);
// Returns null if the app doesn't exist or the tenantId doesn't match
// Never throws on "not found" — callers handle null
SaveAppAsync
// Writes the complete AppDefinition to the database
// Automatically:
// - invalidates Redis cache for this app
// - writes an audit log entry
// - validates tenantId ownership
Task SaveAppAsync(string tenantId, AppDefinition app, string changedByUserId);
SaveLayoutAsync
// Saves the widget placements for a single page
// Used by the designer's drag-drop layout operations
// More granular than SaveAppAsync — only persists placement positions
// Also cache-invalidating and audit-logged
Task SaveLayoutAsync(string tenantId, string appId, string pageId,
IList placements, string changedByUserId);
ExportAppAsync / ImportAppAsync
// Export — produces a complete AppExportBundle
Task ExportAppAsync(string tenantId, string appId, string exportedByUserId);
// Import — applies the bundle with the given options
// ImportOptions controls: conflictStrategy, dryRun, includePermissions
Task ImportAppAsync(string tenantId, AppExportBundle bundle,
ImportOptions options, string importedByUserId);
How Controllers Use the Service
// AppController.cs — representative pattern
[ApiController]
[Route("api/tenants/{tenantId}/apps")]
public class AppController : ControllerBase
{
private readonly IAppStudioService _service;
public AppController(IAppStudioService service) => _service = service;
[HttpGet("{appId}")]
public async Task GetApp(string tenantId, string appId)
{
// TenantId from route is validated against the JWT by middleware
var app = await _service.GetAppAsync(tenantId, appId);
return app == null ? NotFound() : Ok(app);
}
[HttpPost("{appId}")]
public async Task SaveApp(string tenantId, string appId,
[FromBody] AppDefinition app)
{
var changedBy = User.GetUserId(); // From Passport JWT
var saved = await _service.SaveAppAsync(tenantId, app, changedBy);
return Ok(saved);
}
}