Relatude Help

Options

A part of the site only

MapRelatudeCMS takes a route pattern. Combine it with a base path for the URLs, and the database answers below /site while the rest of the application keeps every other path:

builder.AddRelatudeDB(o => o.CreateUrlManager = _ => new DefaultUrlManager(new DefaultUrlManagerOptions { PrimaryBaseAddress = "/site" }));

app.MapRelatudeCMS("site/{**url}");

Finding content another way

ContentResolver.Resolve is the one place where a URL becomes content. Override it to find content another way, such as preview links of your own or an older URL scheme. base.Resolve keeps the default, and Match(node) gives any node its template or code default, as a URL would:

using Relatude.CMS.Engine;
using Relatude.CMS.Models;
using Relatude.DB.CMS.Web.Rendering;

// ?preview=<page id> shows any page to editors; everything else resolves by URL
public sealed class PreviewContentResolver(IRelatudeDBAccessor db, ContentRenderingOptions options)
    : ContentResolver(db, options) {

    protected override ContentMatch? Resolve(HttpRequest request) {
        if (request.HttpContext.User.IsInRole("Editor")
            && Guid.TryParse(request.Query["preview"].ToString(), out var id)
            && Context(request.HttpContext).Admin.TryGet<IPage>(id, out var page)) {
            return Match(page);
        }
        return base.Resolve(request);
    }
}

// Program.cs, before or after AddRelatudeCMS()
builder.Services.AddSingleton<ContentResolver, PreviewContentResolver>();

ContentResolver is a singleton, so an override keeps no state of its own. Context(http) gives the request's RelatudeCmsContext, with the URL taken apart (Url), the node read as the visitor (Node) and the visitor's store (DB).

Another router beside it

A site that also routes an older CMS maps its own transformer beside MapRelatudeCMS(). Let it answer only URLs the database does not address. Then content, files included, always comes first, and the two never answer the same URL:

using Microsoft.AspNetCore.Mvc.Routing;
using Relatude.DB.CMS.Web;
using Relatude.DB.CMS.Web.Rendering;

// LegacyRouter is your own router for the older URLs
public sealed class LegacyRouting(RelatudeCmsContext ctx, ContentResolver content, LegacyRouter legacy) : DynamicRouteValueTransformer {
    public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext http, RouteValueDictionary values) {
        await ctx.AuthenticateAsync();   // routing runs before the authentication middleware
        var ours = content.GetContent(http) != null || content.GetFile(http) != null || content.IsDenied(http);
        return !ours && legacy.TryRoute(http, values) ? values : null!;   // null: no match
    }
}

// Program.cs
builder.AddRelatudeCMS().AddMvcRendering();
builder.Services.AddScoped<LegacyRouting>();
app.MapRelatudeCMS();
app.MapDynamicControllerRoute<LegacyRouting>("{**url}");

Call AuthenticateAsync() before you ask the resolver. Its answer is kept for the whole request, so asking before the visitor is known would read every page as an anonymous visitor.

For a host that builds its routing from scratch, MvcContentRouteTransformer.TrySetRouteValues sets the route values for MVC targets.