// Hands-On tutorial — Movie Dashboard custom component (C# side). // Implements IRenderableComponentV2 (the modern contract) and // IComponentTypeProvider (to expose a stable type-key for the React shell). // // Wiring: // MIB3UX_COMPONENTS.ASSEMBLY_NAME = "Acme.Mib.HandsOn" // MIB3UX_COMPONENTS.CLASS_NAME = "Acme.Mib.HandsOn.MovieDashboardComponent" // The React widget under "acme_movie_dashboard" will render this. using MediaiBox.Cms.Api.Client.Model; using MediaiBox.Cms.FrontEnd.Model.Dao.Item; using MediaiBox.Cms.FrontEnd.Model.Mvc.UI.Component; using MediaiBox.Cms.FrontEnd.Model.UI; using MediaiBox.Cms.FrontEnd.Model.UI.Component; using MediaiBox.Cms.FrontEnd.Model.UI.Context; using MediaiBox.Cms.FrontEnd.Model.Workflow; using Microsoft.AspNetCore.Http; namespace Acme.Mib.HandsOn; public sealed class MovieDashboardSchema : IComponentSchema { public string MediaType { get; set; } = "HANDSON_MOVIES"; public bool ShowFeaturedBadge { get; set; } = true; } public sealed class MovieDashboardData { public int MovieId { get; set; } public string Title { get; set; } = ""; public bool Featured { get; set; } public int AssetsTotal { get; set; } public int AssetsVideoCount { get; set; } public int AssetsSubtitleCount { get; set; } public int AssetsThumbnailCount{ get; set; } public int? YearsSinceRelease { get; set; } } public sealed class MovieDashboardComponent : IRenderableComponentV2, IComponentTypeProvider { private IMibApiClientLibrary? _apiClient; public ContextViewData Context { get; set; } = new(); public ComponentConfiguration Configuration { get; set; } = new(); public ComponentDisplayMode DisplayMode => ComponentDisplayMode.Edit; public string GetComponentType() => "acme_movie_dashboard"; public Task Initialize( IMibApiClientLibrary api, IPublicWorkflowFactory _, IHttpContextAccessor __) { _apiClient = api; return Task.CompletedTask; } public ComponentPermission GetPermission() => new() { Read = PermissionType.Allow, Write = PermissionType.DontCare, Create = PermissionType.DontCare, Delete = PermissionType.DontCare, }; // Required by IComponent. The DisplayWorkflow calls this // via reflection BEFORE MapData/MapSchema run, so the dashboard // wouldn't load if you omit it — even though GetPermission() looks // like it does the same job. public ComponentPermission GetPermissions(List responses) => GetPermission(); public Task MapSchema(object _, CancellationToken __) => Task.FromResult(new MovieDashboardSchema { MediaType = "HANDSON_MOVIES", ShowFeaturedBadge = true, }); public Task MapData(object _, CancellationToken ct) { // No selected id (list / new-record mode) — return an empty // payload, NOT null. Returning null is treated as a render // failure by the framework. if (Context.IDs is null || !Context.IDs.Any()) return Task.FromResult(new MovieDashboardData { MovieId = 0, Title = "" }); var id = Context.IDs.First(); // Real implementations fetch via the MibApi client and build // aggregates. We keep this stub simple (deterministic mock // counts keyed off the movie id) so the federated remote can // demo the render+event surface without depending on actual // asset rows in the DB. Replace with IMibApiClientLibrary.Get(...) // calls once you're ready to wire real queries. return Task.FromResult(new MovieDashboardData { MovieId = id, Title = $"Movie #{id}", Featured = id % 2 == 1, AssetsTotal = 3, AssetsVideoCount = 1, AssetsSubtitleCount = 1, AssetsThumbnailCount = 1, YearsSinceRelease = 5, }); } }