Relatude Help

A module

A module derives from CmsModule (namespace Relatude.DB.CMS.Modules), has a name, and says in Configure what it is made of. The smallest useful module is a model type and some data to start with:

namespace Venues.Models;                         // the module's model types, alone in their namespace

[Node(Id = "87942bcb-5637-401b-bb56-3dbe0ca094fc")]
public interface IVenue : IItem {
    [IntegerProperty(Indexed = true)] int Seats { get; set; }
}
public sealed class VenuesModule : CmsModule {
    public override string Name => "venues";
    public static readonly Guid Workshop = new("fe477557-bf6a-43c2-bf37-64336e3e8045");

    public override void Configure(CmsModuleBuilder module) => module
        .Datamodel(dm => dm.AddNamespace<IVenue>())
        .Seed(1, "A first venue", c => {
            if (c.Store.TryGet<IVenue>(Workshop, out _)) return;      // made by an earlier run
            c.Store.CreateAndInsert<IVenue>(v => { v.Name = "The workshop"; v.Seats = 24; }, Workshop);
        });
}

The events module's types are in Events.Models. IEventPage : IPage, IMeta is a page with StartsUtc, EndsUtc, a Status (Scheduled, Cancelled or Ended) and a Venue. IEventRegistration : IItem has Email, Seats and an Event. The relation to venues is declared there too, so the venues module knows nothing about events. The module uses most of what a module can have:

public sealed class EventsModule : CmsModule {
    public override string Name => "events";
    public override string Title => "Events";
    public override string Description => "Events at venues, with registration and a calendar feed.";

    public static readonly Guid Managers = new("6549f757-c808-4c73-bac2-ea96b826ef49");

    public Guid? ParentPageId { get; init; }              // an option the host sets

    public override void Configure(CmsModuleBuilder module) {
        module.Services.AddSingleton<EventCalendar>();    // the module's own services
        module
            .DependsOn<VenuesModule>()
            .Datamodel(dm => dm.AddNamespace<IEventPage>())
            .TransactionPlugin<EventPagePlugin>()
            .Job("events.close-past", "Marks the events that have ended as ended.", ClosePast)
            .Settings<EventSettingsDefinition>()
            .Seed(1, "Two events", EventSeed.Events)
            .Seed(2, "Events under the host's page", c => EventSeed.ListUnder(c, ParentPageId))
            .UserGroup(Managers, "Event managers")
            .Command("events.upcoming", EventCommands.Upcoming, readOnly: true)
            .Command("events.cancel", EventCommands.Cancel)
            .Endpoints(EventEndpoints.Map)
            .Rendering(o => o.RenderRazorView<IEventPage>("EventPage"))
            .Views()
            .InterfacePlugin();
    }
}
Builder method Adds
Services the module's own services
DependsOn<T>() a module it builds on, and is active only while that one is
RequiresLicense("feature") a feature the installation's license must include
AppliesTo((datamodel, database) => …) which databases it is part of; by default those with the CMS content model
Datamodel(dm => …) model types
TransactionPlugin<T>(), OnStoreInit(c => …) transaction plugins and task runners
Job(name, description, run) a job for the admin UI's scheduler
Settings<TDefinition>() a custom settings set, per database or per server
Seed(n, description, c => …) seed data, in numbered steps
UserGroup(id, name) a user group with a fixed id, made where missing; needs ICMSUserGroup in the datamodel
Log(definition) a custom log, made where missing
OnStoreOpened(c => …) start-up work, after the seed data
Command(type, handler, readOnly) a command for the CMS API
Endpoints(e => …) routes of its own
Rendering(o => …) code defaults, selectors and template kinds
Views() the views, controllers and Razor Pages compiled into the module
InterfacePlugin(entry, folder) its interface plugin

Work done on a store gets a CmsModuleContext: the store as the master admin (Store), the application's services (Services) and the module (Module).