Skip to content

Conversation

@sachatrauwaen
Copy link
Contributor

@sachatrauwaen sachatrauwaen commented Sep 30, 2025

Summary

Add a simple, priority-driven API for setting page title/description/keywords/canonical URL, injecting head tags and meta tags, and collecting user-visible messages. This formalizes page composition logic and decouples it from the rendering pipeline, supporting both the current WebForms pipeline and the new MVC pipeline.

Motivation

  • Unify page composition: Provide a single abstraction for metadata, head elements, and messages across pipelines.
  • Deterministic ordering: Ensure consistent output using explicit priorities.
  • Progress toward MVC/Core: Keeps page state management out of WebForms, easing the hybrid transition.
  • Testability: A small, mockable surface that’s easy to unit test.
  • Skin and module developers can use this serive to contribute to the page elements

Scope

  • Interfaces and models:
    • IPageService
    • PageMessage, PageMessageType
    • PageMeta
    • PageTag
    • PagePriority

Proposed API

  • Title/SEO
    • void SetTitle(string value, int priority = PagePriority.Default)
    • void SetDescription(string value, int priority = PagePriority.Default)
    • void SetKeyWords(string value, int priority = PagePriority.Default)
    • void SetCanonicalLinkUrl(string value, int priority = PagePriority.Default)
    • string GetTitle(), string GetDescription(), string GetKeyWords(), string GetCanonicalLinkUrl()
  • Head content
    • void AddToHead(PageTag tagItem)
    • List<PageTag> GetHeadTags()
    • void AddMeta(PageMeta metaItem)
    • List<PageMeta> GetMetaTags()
  • Messages
    • void AddMessage(PageMessage messageItem)
    • List<PageMessage> GetMessages()
  • Maintenance
    • void Clear()
  • Priority model
    • PagePriority.Site = 10, PagePriority.Page = 20, PagePriority.Default = 100, PagePriority.Module = 200
  • Message model
    • PageMessageType: Success, Warning, Error, Info
    • PageMessage: Heading, Message, MessageType, IconSrc, Priority
  • Meta/head models
    • PageMeta: Name, Content, Priority
    • PageTag: Value, Priority

Example usage

pageService.SetTitle("Products", PagePriority.Page);
pageService.SetDescription("Browse our product catalog.", PagePriority.Page);
pageService.SetCanonicalLinkUrl("https://www.mysite.com/products", PagePriority.Page);

pageService.AddMeta(new PageMeta("viewport", "width=device-width, initial-scale=1.0", PagePriority.Site));
pageService.AddToHead(new PageTag("<link rel="apple-touch-icon" sizes="114x114" href="apple-icon-114.png" type="image/png" />", PagePriority.Page));

pageService.AddMessage(new PageMessage(
    "Saved", "Your product was updated.", PageMessageType.Success, "", PagePriority.Default));

Rendering contract

  • The renderer (WebForms default.aspx , MVC layout) must:
    • Pick the highest-priority values for title/description/keywords/canonical link.
    • Render GetMetaTags() and GetHeadTags() in ascending Priority order.
    • Render GetMessages() in ascending Priority order, with styling determined by PageMessageType.

In default.aspx

  • OnPrerender : set values of PageService

Between the will skins and modules contributions to the pageservice executed with custom info like meta tags

Convenience extensions (Library/Services/Pages/PageExtensions.cs)

These helpers provide simpler overloads for common operations:

// Head content
void IPageService.AddToHead(string tag, int priority = PagePriority.Default);
void IPageService.AddMeta(string name, string content, int priority = PagePriority.Default);

// Messages (default type: Info)
void IPageService.AddMessage(string heading, string message, int priority = PagePriority.Default);

// Messages with explicit type
void IPageService.AddMessage(string heading, string message, PageMessageType messageType, int priority = PagePriority.Default);
void IPageService.AddMessage(string heading, string message, PageMessageType messageType, string iconSrc, int priority = PagePriority.Default);

// Convenience by type
void IPageService.AddSuccessMessage(string heading, string message, int priority = PagePriority.Default);
void IPageService.AddErrorMessage(string heading, string message, int priority = PagePriority.Default);
void IPageService.AddWarningMessage(string heading, string message, int priority = PagePriority.Default);
void IPageService.AddInfoMessage(string heading, string message, int priority = PagePriority.Default);

// Mapping helper for UI layer
DotNetNuke.UI.Skins.Controls.ModuleMessage.ModuleMessageType PageMessageType.ToModuleMessageType();

Example:

pageService.AddToHead("<link rel=\"preload\" href=\"/fonts/ui.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>", PagePriority.Site);
pageService.AddMeta("robots", "max-image-preview:large", PagePriority.Page);

pageService.AddSuccessMessage("Saved", "Settings updated successfully.");
pageService.AddWarningMessage("Heads up", "This feature is experimental.", PagePriority.Page);

Fixes #6737

@sachatrauwaen sachatrauwaen moved this to In review in MVC pipeline Oct 1, 2025
@sachatrauwaen sachatrauwaen changed the title [WIP] Priority-based IPageService for page metadata, head tags, and messages Priority-based IPageService for page metadata, head tags, and messages Oct 3, 2025
@sachatrauwaen sachatrauwaen marked this pull request as ready for review October 3, 2025 15:46
@mitchelsellers
Copy link
Contributor

Moving this content to the right place.

One additional consideration here is the long-standing behavior that modules have been using to override the title property of the default page object and how we handle this.

This for example has been the suggested pathway for years.

var dnnPage = (CDefault) this.Page;
dnnPage.Title = $"Register for {eventDetail.Name} | MyCompany";

I assume that this process will break this behavior

@sachatrauwaen
Copy link
Contributor Author

One additional consideration here is the long-standing behavior that modules have been using to override the title property of the default page object and how we handle this.

This for example has been the suggested pathway for years.

var dnnPage = (CDefault) this.Page;
dnnPage.Title = $"Register for {eventDetail.Name} | MyCompany";

I assume that this process will break this behavior

Yes, you are right this break actually this behavior.

I can maybe include the value of the changed dnnPage.Title in the pageService in the PreRender of the page with a certain priority.
So it will be used untill someone add a title with heiger priority in the pageService.

Copy link
Contributor

@mitchelsellers mitchelsellers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sachatrauwaen looking at this API structure this looks great. I'm curious with regards to the existing API's that would be replaced here.

  1. Can we get an accounting of all of them and get them updated with an [Obsolete] attribute
  2. Can we validate that all would use the same logic

@sachatrauwaen
Copy link
Contributor Author

sachatrauwaen commented Oct 22, 2025

@mitchelsellers with regards to the existing API's

  1. Can we get an accounting of all of them and get them updated with an [Obsolete] attribute

The only API that exist is Skin.AddPageMessage.
And the new api is a hight level api that use Skin.AddPageMessage to render the messages.
I mark Skin.AddPageMessage [Obsolete] and put one Skin.AddPageMessage internal.

  1. Can we validate that all would use the same logic

I redirect all code outside of the Skin class to the new api

  1. Mark MemebershipModule.OnUnverifiedUserSkinInit obsolete, not used in dnn
    Is this ok ?

make PagePriority static
use ToModuleMessageType in Default.cs
…nal method

Modify existing code to use new api
Mark  MemebershipModule.OnUnverifiedUserSkinInit obsolete, not used in dnn
Copy link
Contributor

@bdukes bdukes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Let's switch from Obsolete to DnnDeprecated, but otherwise I think this is good.

@bdukes
Copy link
Contributor

bdukes commented Oct 22, 2025

  1. Mark MemebershipModule.OnUnverifiedUserSkinInit obsolete, not used in dnn
    Is this ok ?

So long as the method is unused, I think marking it obsolete is great. Thanks!

sachatrauwaen and others added 4 commits October 22, 2025 15:39
new DnnDeprecated attribute

Co-authored-by: Brian Dukes <[email protected]>
new DnnDeprecated attribute

Co-authored-by: Brian Dukes <[email protected]>
new DnnDeprecated attribute

Co-authored-by: Brian Dukes <[email protected]>
new DnnDeprecated attribute

Co-authored-by: Brian Dukes <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

[Enhancement]: IPageService for page metadata, head tags, and messages

3 participants