Getting the webpage URL in VisibilityCondition Evaluate method

2025/05/20 8:35 PM

I want to create a visibility condition so that page template properties are only visible on pages that do not start with a specific path. The HttpContext returns the CMS path while editing a page so I tried to get the url for the page being edited using the IWebPageDataContextRetriever and IWebPageUrlRetriever. However the method to override, "Evaluate", does not have an async option.

I asked Kentico AI, and it said "If you require synchronous behavior, you would typically need to call the asynchronous method and block on it (e.g., using .GetAwaiter().GetResult()), but this is generally discouraged in ASP.NET Core applications due to potential deadlocks and performance issues."

Is there another way to get the webpage URL within the VisibilityCondition Evaluate method?

Another way might be to set a property for the template from within the controller but I dont see a way to set template properties this way.

is there some other way I can get the property to hide or disable on a page starting with a specific url?


Environment

  • Xperience by Kentico version: [30.3.1]

  • .NET version: [8]

  • Execution environment: [SaaS|Private cloud (Azure/AWS/Virtual machine)]

  • Link to relevant Xperience by Kentico documentation

Answers

2025/05/21 12:44 AM

The response from the Kentico AI in the docs is correct, but there's also some nuance.

Task.GetAwaiter().GetResult() is the best way to "block" on asynchronous work, but it's still not great. It potentially uses another thread to do work which would have only required 1 thread. It can cause deadlocks but only in frameworks where there's a synchronization context.

There is no synchronization context in traditional ASP.NET Core MVC.

There is a synchronization context in ASP.NET Core Blazor.

So, as long as you are using .GetAwaiter().GetResult() in code that is not executed where there is a synchronization context then the code will work and you won't experience a deadlock. This can be difficult for libraries to ensure, but you know how your application code is used so you can avoid this pitfall.

If you look at the Kentico Community Portal you can see that I use .GetAwaiter().GetResult() in a personalization condition type because the Evaluate method is synchronous.

public override bool Evaluate()
{
    var context = contextAccessor.HttpContext;
    if (context is null)
    {
        return false;
    }

    var identity = context.User.Identity;

    if (identity is null || !identity.IsAuthenticated)
    {
        return false;
    }
    var id = identity as ClaimsIdentity;

    var member = userManager
        .GetUserAsync(context.User)
        .GetAwaiter()
        .GetResult();

    if (member is null)
    {
        return false;
    }

    var assignedMemberBadgeNames = badgeService
        .GetSelectedBadgesFor(member.Id)
        .GetAwaiter()
        .GetResult()
        .Select(b => b.MemberBadgeCodeName);

    return SelectedMemberBadges
        .IntersectBy(
          assignedMemberBadgeNames, 
          selected => selected.ObjectCodeName, StringComparer.OrdinalIgnoreCase)
        .Any();
}

Xperience APIs are gradually being made async (most already are) so you should need to use this workaround less and less over time.

Our APIs that work with Blazor (currently, only the Email Builder) are all async or have an async variant.

Your code should generally be async-first because ASP.NET Core is designed to be async (concurrent, not parallel) from top to bottom.

2025/05/21 1:44 PM

Thanks Sean. Is there any other way that I might get the PageUrl? Can I modify template properties from within the controller? Or add a value to a variable within the properties model from the controller? I would prefer to avoid using Task.GetAwaiter().GetResult() at all in the codebase because from my experience, async methodologies are still not well understood and other devs will see it and use it in places where they shouldn't or its not needed. On a few occasions, CoPilot recommended using Task.GetAwaiter().GetResult() in places where it wasn't needed. I am trying to set a good foundation/example in the code.

2025/05/21 5:46 PM
Answer

The UI Form Components that are part of a Page Builder component's properties are loaded by fetch requests from client-side components in the Page Builder UI. They are part of Xperience, not part of the ASP.NET Core MVC pipeline that processes requests for your pages. That is why HttpContext doesn't include the information you are looking for. The request is made to Xperience's internal endpoints, not your page's Controller.

So, no, there isn't another option. If there was, I'd be using it the Kentico Community Portal!

My recommendation is to leave a friendly, descriptive comment in your code above your call to .GetAwaiter().GetResult() that explains why it is there and why developers should use async and await elsewhere in the project. Education over ignorance 😉.

2025/05/21 6:41 PM

Thanks Sean. Unfortunately even if I use IWebPageDataContextRetriever  and then get the URL, like you said, it doesnt have the page context so there is no way to figure out which page I am on to determine if I should hide a property or not

2025/05/21 6:59 PM

Ah, yeah. I hadn't checked those APIs to see if they actually have access to that context, but that makes sense. The UI Form Components and related technology are not designed to have knowledge of the page they are used on, primarily because they can be used in other areas of Xperience's Admin UI, not just the Page Builder.

Slightly related, you can send the page context on a POST from your page's view by including the page data in your HTML form on the page. But, similar to what I mentioned above, this would be POSTing to your endpoint, not one of Xperience's.

2025/05/21 10:03 PM

Trevor helped me find the way with IHttpContextAccessor, which has the pageid hidden in the request path!!

contextAccessor.HttpContext!.Request.Form["path"].ToString().Split("/")[1].Split("_")[1];

To answer this question, you have to login first.