Can I have different Section properties based on the Channel name?
I have 2 website channels, both using the same sections. Is there a way I can differentiate the section properties based on the Channel I'm in?
The Visibility conditions only works with values entered into another input in the form.
I would like to do something like:
[DropDownComponent(Label = "Branding color", Order = 1, Options = "Primary;Primary\nSecondary;Secondary\nAccent;Accent")]
[VisibleIfEqualTo(nameof(WebsiteChannelName), "Channel1", StringComparison.OrdinalIgnoreCase)]
public string BrandingColor { get; set; }
or
public string ChannelProperties => WebsiteChannelName.Equals("Channel1") ? "Primary;Primary\nSecondary;Secondary\nAccent;Accent" : "text-bg-primary-1;Dark Blue\ntext-bg-primary-2;White\ntext-bg-primary-3;Sand";
[DropDownComponent(Label = "Branding color", Order = 1, Options = ChannelProperties)]
public string BrandingColor { get; set; }
Environment
- Xperience by Kentico version: [28.4.1]
- .NET version: [8]
- Deployment environment: [Local|Azure]
- Link to relevant Xperience by Kentico documentation
Answers
I think you might need to build a custom visibility condition.
In the evaluate method you could inspect the current channel name and handle appropriately.
They also support dependency injection.
Hey @Liam Thanks for pointing me to the custom visibility conditions.
I was able to create a custom condition and check the channel Name, however when calling CMS.Core.Service.Resolve<IWebPageDataContextRetriever>().WebsiteChannelName
it always returns my first channel.
It seems that on the section it doesn't resolve the correct channel context...
Any idea how I can retrieve the correct channel context?
fyi, when I use IWebsiteChannelContext
through depency injection in a ViewComponent, I do get the correct Channel Name.
@nele.debrouwer
I can think of 2 options at the moment (but there are possibly others).
Create a custom visibility condition
public class ChannelVisibilityConditionAttribute(string channelName)
: VisibilityConditionAttribute
{
public string ChannelName { get; set; } = channelName ?? "";
}
[VisibilityConditionAttribute(typeof(ChannelVisibilityConditionAttribute))]
public class ChannelVisibilityCondition(IWebsiteChannelContext websiteChannelContext)
: VisibilityCondition<ChannelVisibilityConditionProperties>
{
private readonly IWebsiteChannelContext websiteChannelContext = websiteChannelContext;
public override bool Evaluate(IFormFieldValueProvider formFieldValueProvider)
{
return string.Equals(
websiteChannelContext.WebsiteChannelName,
Properties.ChannelName,
StringComparison.OrdinalIgnoreCase);
}
}
public class ChannelVisibilityConditionProperties
: VisibilityConditionProperties
{
public string ChannelName { get; set; } = "";
}
You can use it in your component properties like this:
public class MySectionProperties : ISectionProperties
{
[ChannelVisibilityCondition("DancingGoatPages")]
[DropDownComponent(
Label = "Pages Color",
Options = "Primary;Primary\nSecondary;Secondary")]
public string PagesColor { get; set; } = "";
[ChannelVisibilityCondition("DancingGoatNew")]
[DropDownComponent(
Label = "New Color",
Options = "Accent;Accent\nSpecial;Special")]
public string NewColor { get; set; } = "";
}
This will only show each property for the specified channel.
Create a custom options provider
Another option is to create a custom IDropDownOptionsProvider
, which can populate the dropdown based on the context of the request, which in this case would be the IWebsiteChannelContext
.
You'd need to create a custom object type, which I've called PageBuilderColorInfo
in the example below:
public class ChannelColorDropDownOptionsProvider(
IWebsiteChannelContext websiteChannelContext,
IInfoProvider<PageBuilderColorInfo> colorInfoProvider)
: IDropDownOptionsProvider
{
private readonly IWebsiteChannelContext websiteChannelContext = websiteChannelContext;
private readonly IInfoProvider<PageBuilderColorInfo> colorInfoProvider = colorInfoProvider;
public async Task<IEnumerable<DropDownOptionItem>> GetOptionItems()
{
var options = await colorInfoProvider.Get()
.WhereEquals(
nameof(PageBuilderColorInfo.PageBuilderColorWebsiteChannelID),
websiteChannelContext.WebsiteChannelID)
.GetEnumerableTypedResultAsync();
return options.Select(i => new DropDownOptionItem()
{
Text = i.PageBuilderColorDisplayName,
Value = i.PageBuilderColorName
});
}
}
You would use this custom provider in your component properties like this:
public class MySectionProperties : ISectionProperties
{
[DropDownComponent(
Label = "Pages Color",
DataProviderType = typeof(ChannelColorDropDownOptionsProvider))]
public string PagesColor { get; set; } = "";
}
This approach has a more complex setup, but simplifies the component properties and allows you to dynamically manage the options in the administration UI.
You can see an example of a custom IDropDownOptionsProvider
in the Quickstart Guides.
Hey @seangwright, Thanks for the given options.
I implemented the first option (Visibility condition), but as mentioned in my previous comment, the website channel context always returns my first channel, unless I acces Channel_B via it's own URL.
We'll have content editors managing both website channels, so they won't always change the URL when they need to switch between website channels.
It seems the context does not get properly resolved in the Visibility condition.
Any idea how I can retrieve the correct channel context?
@nele.debrouwer
Hm, I'm guessing you are accessing the administration through one of the website channel domains?
If so, don't do this ð.
The administration is not a website channel. It should be accessed by a non-channel domain that is only used for the administration.
If you look at the Community Portal project you can see we have this separation in the local environment. The Community Portal is deployed to SaaS which enforces this separation.
Here's an example of the system domains in the SaaS environment (which are used to access the administration UI):
You can also see the error that is displayed if you try to access a website channel from the system (administration) domain:
Update #1
I just realized the use-case I was testing for this solution didn't match your use-case. So, in addition to my example solutions and my guidance above (which still applies), I also acknowledge we have a gap in the product right now.
You can't use IWebsiteChannelContext
or IPageBuilderDataContextRetriever
in Page Builder UI Form Components or related types (visibility conditions, custom options providers).
We have some internal services to access this context, but they aren't available for you to use. We'll look at exposing this information in the future.
In the meantime, here's a ... "workaround" that you can use ð:
public interface IPageBuilderUtilityContextRetriever
{
Task<PageBuilderUtilityContext> Retrieve();
}
public record PageBuilderUtilityContext(
string WebsiteChannelName,
int WebsiteChannelID,
IWebPageFieldsSource Webpage);
public class PageBuilderUtilityContextRetriever(
IHttpContextAccessor contextAccessor,
IInfoProvider<ChannelInfo> channelInfoProvider,
IContentQueryExecutor queryExecutor)
: IPageBuilderUtilityContextRetriever
{
private readonly IHttpContextAccessor contextAccessor = contextAccessor;
private readonly IInfoProvider<ChannelInfo> channelInfoProvider = channelInfoProvider;
private readonly IContentQueryExecutor queryExecutor = queryExecutor;
private PageBuilderUtilityContext? context = null;
public async Task<PageBuilderUtilityContext> Retrieve()
{
if (context is not null)
{
return context;
}
int websiteChannelID = 0;
int webPageID = 0;
var httpContext = contextAccessor.HttpContext;
string path = httpContext.Request.Form["path"].FirstOrDefault() ?? "";
string pattern = @"webpages-(\d+)/([^_/]+)_(\d+)";
var match = Regex.Match(path, pattern);
if (match.Success)
{
websiteChannelID = int.TryParse(match.Groups[1].Value, out int channelID) ? channelID : 0;
webPageID = int.TryParse(match.Groups[3].Value, out int pageID) ? pageID : 0;
}
var channels = await channelInfoProvider.Get()
.Source(s => s.Join<WebsiteChannelInfo>(nameof(ChannelInfo.ChannelID), nameof(WebsiteChannelInfo.WebsiteChannelChannelID)))
.Where(w => w.WhereEquals(nameof(WebsiteChannelInfo.WebsiteChannelID), websiteChannelID))
.Columns(nameof(ChannelInfo.ChannelName))
.GetEnumerableTypedResultAsync();
string websiteChannelName = channels
.Select(s => s.ChannelName)
.FirstOrDefault() ?? "";
var query = new ContentItemQueryBuilder()
.ForContentTypes(q => q.ForWebsite([webPageID], false));
var pages = await queryExecutor.GetMappedWebPageResult<IWebPageFieldsSource>(query);
var webPage = pages.FirstOrDefault();
context = new(websiteChannelName, websiteChannelID, webPage);
return context;
}
}
Register this service with DI:
services.AddScoped<
IPageBuilderUtilityContextRetriever,
PageBuilderUtilityContextRetriever>();
And then inject into your visibility condition class constructor.
You'll probably want to add better guards in the Retrieve()
method or maybe convert it to TryRetrieve()
in case the context can't be retrieved.
Hey @seangwright, Thanks for the possible workaround. This works as expected.
We however think we experienced another gap when using a custom visibility condition as a section property.
When we deployed to our Staging environment we received the following error during the deploy to our 'live' environment:
Run dotnet publish -c Release -p:AdminAttached=false -o C:\Users\runneradmin\AppData\Local\Microsoft\dotnet/live
Error: D:\a\Conditions\ChannelVisibilityCondition.cs(10,102): error CS0246: The type or namespace name âVisibilityCondition<>â could not be found (are you missing a using directive or an assembly reference?)
Error: D:\a\Conditions\ChannelVisibilityCondition.cs(9,6): error CS0246: The type or namespace name âVisibilityConditionAttributeAttributeâ could not be found (are you missing a using directive or an assembly reference?)
Error: D:\a\Conditions\ChannelVisibilityCondition.cs(9,6): error CS0246: The type or namespace name âVisibilityConditionAttributeâ could not be found (are you missing a using directive or an assembly reference?)
Error: D:\a\ChannelVisibilityCondition.cs(14,39): error CS0246: The type or namespace name âIFormFieldValueProviderâ could not be found (are you missing a using directive or an assembly reference?)
Error: D:\a\Conditions\ChannelVisibilityConditionProperties.cs(5,57): error CS0246: The type or namespace name âVisibilityConditionPropertiesâ could not be found (are you missing a using directive or an assembly reference?)
Error: Process completed with exit code 1.
To our live environment we deploy withouth the admin attached as explained on Deploy without the administration
However, the VisibilityConditionProperties an VisibilityConditionAttributeAttribute classes are part of the Assembly Kentico.Xperience.Admin.Base which does not get deployed when you deploy without the administration.
At the moment we've added a reference in our solution to the Kentico.Xperience.Admin.Base assembly, but this exposes the administration through the website channel domain, which we don't want.
Any idea on how we can resolve this issue?
@nele.debrouwer
The VisibilityConditionAttribute
type is actually in Kentico.Xperience.Admin.Base.Shared
, which is a special assembly that is "shared" between Kentico.Xperience.WebApp
and Kentico.Xperience.Admin
.
The Kentico.Xperience.Admin.Base.Shared
assembly is not published as a separate NuGet package and instead should be accessed through Kentico.Xperience.WebApp
or Kentico.Xperience.Admin
.
This means your VisibilityCondition
and VisibilityConditionProperties
classes would need to go in a separate "admin" .NET project, which is only included during administration deployments.
You would then need to create a "shared" Admin project that references the Kentico.Xperience.WebApp
NuGet package and is itself referenced by your ASP.NET Core project and your "admin" .NET project.
This shared project would contain the VisibilityConditionAttribute
class.
Shared .NET project
This project references the Kentico.Xperience.WebApp
NuGet package.
public class ChannelVisibilityConditionAttribute(string channelName)
: VisibilityConditionAttribute
{
public string ChannelName { get; set; } = channelName ?? "";
}
ASP.NET Core project
This project references the Kentico.Xperience.WebApp
NuGet package and the Shared project.
It also "conditionally" references the Admin project (only when performing a deployment including the administration).
public class MySectionProperties
: ISectionProperties
{
[ChannelVisibilityCondition("DancingGoatPages")]
[DropDownComponent(
Label = "Pages Color",
Options = "Primary;Primary\nSecondary;Secondary")]
public string PagesColor { get; set; } = "";
}
Administration .NET project
This project references the Shared project and the Kentico.Xperience.Admin
NuGet package.
By referencing the Shared project, the ChannelVisibilityConditionAttribute
can be assigned to the ChannelVisibilityCondition
class, gluing them together for the administration/Page Builder UI.
[VisibilityConditionAttribute(
typeof(ChannelVisibilityConditionAttribute))]
public class ChannelVisibilityCondition(...)
: VisibilityCondition<ChannelVisibilityConditionProperties>
{
// ...
}
public class ChannelVisibilityConditionProperties
: VisibilityConditionProperties
{
public string ChannelName { get; set; } = "";
}
I hope this makes sense! Deploying without the administration is possible but it requires a little more planning and architecture by your team.
To answer this question, you have to login first.