Url.Action does not work in CMS 12?



We have just upgraded to CMS 12 and we have this in our view:

<form id="searchbox" action="@Url.Action("Index", "SearchPage")" method="get" autocomplete="off" class="page-searchbox tracked-search">

In CMS 11 I get this: 

<form id="searchbox" action="/sv/admin/soksida" method="get" autocomplete="off" class="page-searchbox tracked-search">

which is correct, but in CMS 12 I get this:

<form id="searchbox" action="/modules/App/SearchPage" method="get" autocomplete="off" class="page-searchbox tracked-search">

What am I missing?



Aug 24, 2022 9:07


I added:

_ = endpoints.MapControllerRoute(name: "Default", pattern: "{controller}/{action}/{id?}");

and that gave me:

<form id="searchbox" action="/SearchPage/Index" method="get" autocomplete="off" class="page-searchbox tracked-search">

Still missing something.

Come to think aobut it, I think it is more strange that it works in CMS 11? Shouldn't 


be tha way to go?


Edited, Aug 24, 2022 11:10

Kristoffer I was looking into this myself and believe its due to the way the Middle Layer for routing has changed in .Net Core. As you mentioned it might be better switching to Url.ContentURL or if you have a view model at your disposle store the URL in a property. 

@{ using (Html.BeginForm(null, null, Html.ViewContext.IsInEditMode() ? FormMethod.Post : FormMethod.Get, new { @action = Model.Layout.SearchActionUrl }))
        <input type="text" class="search-query" name="q" id="SearchKeywords" placeholder="@Html.Translate("/mainnavigation/search")" />
        <input type="submit" class="searchButton" id="SearchButton" value="" />


        <div class="searchContainer" id="searchBar">
            <form class="searchWrap" method="get" action="@Url.ContentUrl(Model.SearchResultsPage)#results-top">

                <div class="searchForm">
                    <label for="searchSite" class="offscreen">Search site</label>
                    <input type="text" id="searchSite" maxlength="50" name="@Configuration.Constants.QueryString.Query" placeholder="@Model.SearchBoxPlaceholderText" />
                    <button type="submit" class="icon searchButton">
                            <use xlink:href="#svg-search"></use>
Aug 24, 2022 13:39

Hi Minesh!

Yes, I will solve it in some of the ways above, it will work just fine. I just wanted to know that is does not work and that I'm not just missed to add something.
I will start like that and we will see.



Aug 24, 2022 16:59

The reason is becasue of how middleware works and the order of exceution changed between the net 5/ 6 and full framework.  Using contenturl is the way to go.

Aug 24, 2022 21:09

Have you tried using @Html.BeginContentForm instead? I think it's a new addition to CMS 12.  It has a lot of methods so I created an extension method to simplify it in a couple of places:

public static class HtmlHelperExtensions
    public static MvcForm BeginContentForm<T>(
        this IHtmlHelper<T> htmlHelper, 
        ContentReference? contentReference,
        FormMethod formMethod,
        string formClass)
        return htmlHelper.BeginContentForm(
            new { @class = formClass });

Then on the razor file I have this:

@using (Html.BeginContentForm(Model.SiteSettings.SearchPage, FormMethod.Get, "footer-search__form"))
    <div class="p-footer__input-group">
        <input type="text"
               required />
        <button type="submit" class="footer-search__button">Search</button>
Aug 26, 2022 8:02

I'm running into this as well for Url.Action no longer working with content urls. I think the best bet in many situations may be to inject IUrlResolver in your view and call getUrl with VirtualPathArguments as you can set Action on it. Perhaps a UrlHelper extension might be a good idea for syntactic sugar.

Unfortunately, there are cases were link generation might not be in your complete control, so I would love to see content aware implementations of IUrlHelper and LinkGenerator

Sep 28, 2022 20:51

I'm experiencing this issue as well - @Url.Action(...) creates the MVC route rather than the content route.

What puzzles me is that Episerver Foundation tries to achieve this as well:



Oct 04, 2022 13:13

@Quan, can you please add this? You're literally doing the exact same thing in RedirectToContentResultExecutor. Also, while you're at it - can you add RouteValues to RedirectToContent/RedirectToContentResult and add it to the URL generator in RedirectToContentResultExecutor? :) Should be a easy thing for you, that makes life easier for us!

Nov 03, 2022 9:07

This is an interesting problem we have encountered too while upgrading a solution to the v12. How do you handle the query parameters when using Url.ContentUrl? Do you prefer to write them by hand or is there any extension method that can generate the link with query parameters given an anonymous object?

Feb 17, 2023 10:08

I've built the following helper methods to replace Url.Action and supply additional route values - it's worked out well so far

    public static class UrlHelperExtensions
        public static string ContentAction(this IUrlHelper urlHelper, string action)
            return ContentAction(urlHelper, action, null, null, null);

        public static string ContentAction(this IUrlHelper urlHelper, string action, object values)
            return ContentAction(urlHelper, action, null, null, values);

        public static string ContentAction(this IUrlHelper urlHelper, string action, ContentReference contentLink, string language, object values)
            var routeValues = values as RouteValueDictionary ?? new RouteValueDictionary(values);
            routeValues["action"] = action;
            return ContentUrl(urlHelper, contentLink, language, routeValues);

        public static string ContentUrl(this IUrlHelper urlHelper, ContentReference contentLink, string language, object values)
            var routeValues = values as RouteValueDictionary ?? new RouteValueDictionary(values);
            return ContentUrl(urlHelper, contentLink, language, routeValues);

        public static string ContentUrl(this IUrlHelper urlHelper, ContentReference contentLink, string language, RouteValueDictionary routeValues)
            if (ContentReference.IsNullOrEmpty(contentLink) || string.IsNullOrEmpty(language))
                var feature = urlHelper.ActionContext.HttpContext.Features.Get<IContentRouteFeature>();
                if (feature != null)
                    var routedContentData = feature.RoutedContentData;
                    language = string.IsNullOrEmpty(language) ? routedContentData.RouteLanguage : language;
                    contentLink = ContentReference.IsNullOrEmpty(contentLink) ? routedContentData.Content?.ContentLink : contentLink;

            if (!ContentReference.IsNullOrEmpty(contentLink))
                var action = routeValues["action"] as string;


                var arguments = new VirtualPathArguments {
                    Action = action,
                    RouteValues = routeValues

                var resolver = urlHelper.ActionContext.HttpContext.RequestServices.GetRequiredService<UrlResolver>();
                return resolver.GetUrl(contentLink, language, arguments);

            return string.Empty;

    public static class IDictionaryExtensions
        public static void RemoveEmptyValues<TKey, TValue>(this IDictionary<TKey, TValue> source)
            var keys = source.Keys.ToList();
            foreach (var key in keys) {
                var value = source[key] as object;
                if (value == null ||
                    (value is string s && string.IsNullOrEmpty(s)) ||
                    (value is StringValues sv && StringValues.IsNullOrEmpty(sv)))
Edited, Feb 17, 2023 14:54
huseyinerdinc - Feb 17, 2023 15:16
Great, thanks!
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.