Skip to content

Create settings necessary for an MVC Pipeline#7290

Open
donker wants to merge 11 commits into
dnnsoftware:feature/mvc-pipelinefrom
donker:feature/mvc-pipeline-settings
Open

Create settings necessary for an MVC Pipeline#7290
donker wants to merge 11 commits into
dnnsoftware:feature/mvc-pipelinefrom
donker:feature/mvc-pipeline-settings

Conversation

@donker
Copy link
Copy Markdown
Contributor

@donker donker commented May 22, 2026

This PR creates the settings necessary for the MVC Pipeline. These are the following:

  1. A site setting which determines the entire site's pipeline behavior: WebForms (default), MVC or Auto.The value becomes part of the PortalSettings. In the UI this can be found on the PB under Site Settings > Site Behavior > More at the bottom of the panel.
  2. A page setting which tells DNN how to render: Inherited (default), WebForms, MVC. The setting is stored in the TabSettings. It is visible in the UI on the PB under Pages > Advanced > More towards the bottom.
  3. A new field "MvcControlClass" on the ModuleControls table which informs DNN whether the module control supports the new pipeline. The value will be a class reference or empty in case there is no explicit support for MVC. There is support for this field in the PB extensions module (on the module control editor) and in the module's manifest (.dnn file)

This is the first PR in a series of PRs that aim to build out the MVC Pipeline functionality in DNN. It targets a feature branch as all PRs will need to be in before this can be merged into the main branch of the project. The reason for a more granular approach to the PRs is to make them easier to verify for the reviewers.

bool ShowQuickModuleAddMenu { get; }

/// <summary>Gets the pipeline type for the portal.</summary>
string PagePipeline { get; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need a strategy for adding fields to interfaces without making a breaking change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Any ideas? IPortalSettingsPlus will get very silly very soon. Then I'd prefer IPortalSettings2.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In this case, there is not a need to expose it from the interface (the places where the property is currently used, the variable's type is PortalSettings, not IPortalSettings). But it would obviously be helpful to switch references to the interface, or have it available for 3rd parties.

I see four options for adding members to an interface (in rough order of my preference).

  1. We create smaller named interfaces with only a small handful of properties, e.g. public interface IRenderingSettings { PagePipeline.PortalRenderingPipeline PagePipeline { get; } }
  2. We add a version number, e.g. public interface IPortalSettingsV2 : IPortalSettings { PagePipeline.PortalRenderingPipeline PagePipeline { get; } }
  3. We switch to abstract base classes instead of interfaces, e.g. public abstract class PortalSettingsBase { PagePipeline.PortalRenderingPipeline PagePipeline { get; } }
  4. We break interfaces when necessary on major versions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But as long as you are not actively creating objects in your module to implement the interface, it wouldn't break, no? I mean: if I use an IPortalSettings in my module, my module wouldn't break because a new property gets added. Only when I create an object that implements the IPortalSettings would things go South, no? If this is true, then should we not have a strategy where certain interfaces are marked solely for consumption?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are basically 2 strategies to add to an interface I have seen.

  1. Extensions methods, if the new members are related, then they could be grouped into some extension to that basic interface. I have done so for the portal styles with .GetPortalStyles() which extends IPortalSettings or some such.
  2. I have seen in many libraries including some from Microsoft when interfaces are simply versioned and intherit an older interface within the new one. One example is Visual Studio SDK itself that has IVsSolution, IVsSolution2, IVsSolution3, IVsSolution4, IVsSolution5, IVsSolution6. It is ugly but it works for both adding new stuff, changing stuff and removing deprecated stuff. And if we have an inherithance chain, consumers only have to migrate to the new type and only handle what has changed. This would be one example https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Clients/NuGet.VisualStudio/Extensibility/IVsPackageInstaller2.cs

With todays tooling (intellisense and AI), I personally don't have anything against option 2 which could allow us to move quite faster in some areas.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, if the thing is not meant for public usage, in this case I think people would want to be able to access those settings.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@bdukes regarding:

I'm happy to add numbered versions of interfaces. Do we want to match major DNN version (e.g. this field would be on IPortalSettings11), or just start at 2 and make changes any time we want?

I am of the opinion we got go 2, 3, 4 that way we don't even need to wait for next major to move forward on "user non-breaking changes" as long as we keep the previous interface alive for 2 majors.

Copy link
Copy Markdown
Contributor

@valadas valadas May 28, 2026

Choose a reason for hiding this comment

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

The only thing I did not test and this may be pretty simple but for DI I think we would do something like:

services.AddScoped<IService, Service>();
services.AddScoped<Iservice2, Service>();

Now that brings another interesting thought though, Service if it was public now has the breaking changes for anyone that inherited it, unless it was sealed. So we may need a Service and a Service2 as well... Maybe we should start sealing new classes we are adding unless there is a good reason to inherit from them. That might simplify future maintenance...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It shouldn't be breaking to inherit from PortalSettings if we add IPortalSettings2, because the base class will already implement that interface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are you saying that if we define the interface as internal then we don't need to worry about backward compatibility?

I was thinking, Yes.

If the interface is internal, we only need to preserve compatibility for components we control and ship together.

Comment thread DNN Platform/DotNetNuke.Abstractions/Portals/PagePipelineConstants.cs Outdated
Comment thread DNN Platform/Library/Data/DataProvider.cs Outdated
Comment thread DNN Platform/Library/Data/DataProvider.cs

PortalSettings.PortalAliasMapping GetPortalAliasMappingMode(int portalId);

string GetPortalPagePipeline(int portalId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adding a member to an interface here as well, needs discussion

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would be a scenario where an extension methods might suffice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But it would still be good to add it to the interface at some point in the future

Comment thread DNN Platform/Library/Services/Upgrade/Upgrade.cs Outdated
Comment thread DNN Platform/Library/Services/Upgrade/Upgrade.cs
donker and others added 10 commits May 27, 2026 09:03
Co-authored-by: Brian Dukes <bdukes@engagesoftware.com>
…/Dnn.Extensions/App_LocalResources/Extensions.resx

Co-authored-by: Brian Dukes <bdukes@engagesoftware.com>
…/Dnn.Pages/App_LocalResources/Pages.resx

Co-authored-by: Brian Dukes <bdukes@engagesoftware.com>
…/Dnn.SiteSettings/App_LocalResources/SiteSettings.resx

Co-authored-by: Brian Dukes <bdukes@engagesoftware.com>
Co-authored-by: Brian Dukes <bdukes@engagesoftware.com>
Co-authored-by: Brian Dukes <bdukes@engagesoftware.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants