Try our conversational search powered by Generative AI!

GetVirtualPathSegment in custom segment is never called

Vote:
 

Ahoy

Trying to map /SomePage/?tag=myTag to a custom route. I've implemented RouteDataMatch, so that /SomePage/myTag returns the content it's supposed to.

My action methods looks like this:

public ActionResult Index(MyPageType currentPage, string tag) { ... }


My custom route segment:

public class TagSegment : SegmentBase
    {
        public TagSegment(string name) : base(name)
        {
        }

        public override bool RouteDataMatch(SegmentContext context)
        {
            // This works, so it's irrelevant
        }

        public override string GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values)
        {
            throw new NotImplementedException();
        }
    }

And then I hook it up in Global.asax.cs:

            var urlSegmentRouter = ServiceLocator.Current.GetInstance<IUrlSegmentRouter>();
            urlSegmentRouter.RootResolver = s => s.StartPage;

            var routingParams = new MapContentRouteParameters
            {
                SegmentMappings = new Dictionary<string, ISegment> { { "tag", new TagSegment("tag") } },
                Direction = SupportedDirection.Both,
                UrlSegmentRouter = urlSegmentRouter
            };

            RouteTable.Routes.MapContentRoute("Tags", "{language}/{node}/{tag}", new { action = "Index" }, routingParams);

And when using @Url.Action("Index", new { tag = @tag.Value }) , I just get a link to /myPage/?tag=myTag. I've tried adding other stuff to the route values, with no success. Changing the registered route in any way also causes the entire thing to break down (RouteDataMatch no longer gets called). But my GetVirtualPathSegment is just never called.

I would expect the name of my segment ("tag") to be the deciding factor for whether or not my route segment is given an attempt to resolve itself...? As in; I'm explicitly adding a "tag" parameter to my link, and would thus expect the segment with the name "tag" to be called at some point when generating links.

#87567
Jun 17, 2014 11:38
Vote:
 

Try insert your custom route at start of routes collection...only the first route that can handle the request gets to handle it. Since the other routes actually can handle the request (map to /myPage/?tag=myTag), it will...

#87571
Jun 17, 2014 12:44
Vote:
 

Tried adding it as an IInitializableModule. That led to my routes being registered way earlier, and the GetVirtualPathSegment method was in fact called as expected. However, it seems that it for some reason just takes over every other route in the system, which means no pages but those that I actually route stuff for works... And if my RouteDataMatch returns false, it just gives me 404 back - aren't all other routes supposed to try and match their logic first? I don't get this.

Looking into it more tomorrow, but thanks for the input.

#87601
Jun 17, 2014 19:07
Vote:
 

Did you ever manage to get this working?

I am having a similar issue - my first route using MapRouteContent seems to take over the other MapRouteContent routes below it in the list when rendering out going Urls. All of these are also called inside an IInitializableModule and have segment mappings.

Any ideas greatly appreciated.

#109754
Oct 14, 2014 12:53
Vote:
 

You could try to use an overload to UrlResolver.GetUrl that takes a VirtualPathArguments as parameter. And in the arguments add 

args.RouteValues["tag"] = "yourtag"

#109781
Oct 14, 2014 17:45
Vote:
 

Unfortunately that hasn't worked. Still the second route is not rendered as a friendly Url. If I use either route only it renders correctly or if I swap around always the second one fails.  Ive replicated in Alloy MVC site using latest versions.

Am I doing anything incorrect in routes or is this a bug?

The following produces in Html using both UrlHelper.Action and UrlResolver.GetUrl:

/en/about-us/product-details/P456/

/en/about-us/skill-level/?skilllevel=2

[InitializableModule]
    [ModuleDependency(typeof(ServiceContainerInitialization))]
    public class RoutingConfig : IInitializableModule
    {
        public void RegisterRoutes(RouteCollection routes)
        {
            var urlSegmentRouter = ServiceLocator.Current.GetInstance<IUrlSegmentRouter>();
            urlSegmentRouter.RootResolver = s => s.StartPage;

            routes.MapContentRoute(
           "ProductDetail",
           "{language}/{node}/{skuid}/{action}",
           new { controller = "ProductDetails", action = "Index", skuid = string.Empty },
           new MapContentRouteParameters
           {
               SegmentMappings = new Dictionary<string, ISegment>() { { "skuid", new SimpleSegment("skuid") } },
               UrlSegmentRouter = urlSegmentRouter,
               Constraints = new { node = new ContentTypeConstraint<ProductDetailsPage>() },
               Direction = SupportedDirection.Both
           });

            routes.MapContentRoute(
             "skillLevel",
             "{language}/{node}/{skilllevel}/{action}",
             new { controller = "SkillLevel", action = "Index", skillLevel = @"^[0-9]+$" },
             new MapContentRouteParameters
             {
                 SegmentMappings = new Dictionary<string, ISegment>() { { "skilllevel", new SimpleSegment("skilllevel") } },
                 UrlSegmentRouter = urlSegmentRouter,
                 Constraints = new { node = new ContentTypeConstraint<SkillLevelPage>() },
                 Direction = SupportedDirection.Both
             });
        }

        public void Initialize(InitializationEngine context)
        {
            this.RegisterRoutes(RouteTable.Routes);
        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void Preload(string[] parameters)
        {
        }
    }

public class ContentTypeConstraint<TContentType> : IRouteConstraint where TContentType : IContent
    {
        private readonly bool matchInheritedTypes;
        private readonly IContentLoader contentLoader;

        public ContentTypeConstraint(bool matchInheritedTypes = false)
        {
            this.matchInheritedTypes = matchInheritedTypes;
            this.contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
        }

        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            var nodeToken = values[RoutingConstants.NodeKey];

            if (nodeToken == null)
            {
                return false;
            }

            IContent content = this.contentLoader.Get<IContent>(nodeToken as ContentReference);
            Type requiredContentType = typeof(TContentType);
            Type contentType = content.GetType().BaseType; // match on BaseType because contentType is the Castle proxy type.

            if (this.matchInheritedTypes)
            {
                return contentType != null && contentType.IsAssignableFrom(requiredContentType);
            }

            return contentType == requiredContentType;
        }
    }

public class SimpleSegment : SegmentBase
    {
        public SimpleSegment(string name) : base(name)
        {
        }

        public override bool RouteDataMatch(SegmentContext context)
        {
            string path = context.RemainingPath;

            if (string.IsNullOrEmpty(path))
            {
                if (!context.Defaults.ContainsKey(this.Name))
                {
                    return false;
                }

                context.RouteData.Values[this.Name] = context.Defaults[this.Name];
                return true;
            }

            context.RemainingPath = string.Empty;
            context.RouteData.Values[this.Name] = path.TrimEnd('/');
            context.RouteData.Values.Add(RoutingConstants.NodeKey, context.RouteData.DataTokens[RoutingConstants.NodeKey]);

            return true;
        }

        public override string GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values)
        {
            if (!values.ContainsKey(this.Name))
            {
                return null;
            }

            return values[this.Name].ToString();
        }
    }



#111710
Oct 17, 2014 17:04
Vote:
 

For segments that is parameters to an action (like I assume skillevel is) you do not need to add a segment to SegmentMappings dictionary. They will be assigned to an instance of ParameterSegment.

#111781
Oct 21, 2014 13:21
Vote:
 

Thanks for reply Johan.

Even with SegementMappings removed, still the 2nd route is never written out friendly.

Trimmed down route now looks like:

var urlSegmentRouter = ServiceLocator.Current.GetInstance<IUrlSegmentRouter>();
            urlSegmentRouter.RootResolver = s => s.StartPage;

            routes.MapContentRoute(
           "ProductDetail",
           "{language}/{node}/{skuid}/{action}",
           new { controller = "ProductDetails", action = "Index", skuid = string.Empty },
           new MapContentRouteParameters
           {
               Constraints = new { node = new ContentTypeConstraint<ProductDetailsPage>() },
               Direction = SupportedDirection.Both
           });

            routes.MapContentRoute(
             "skillLevel",
             "{language}/{node}/{skilllevel}/{action}",
             new { controller = "SkillLevel", action = "Index", skilllevel = @"^[0-9]+$" },
             new MapContentRouteParameters
             {
                 Constraints = new { node = new ContentTypeConstraint<SkillLevelPage>() },
                 Direction = SupportedDirection.Both
             });

Any ideas or have you got a project set up working with route configuration similar?

This is broken in Alloy example so can't be anything else interfering

#111886
Oct 21, 2014 13:45
Vote:
 

Anybody with any idea as to why this isn't working?

#112577
Oct 30, 2014 13:12
* 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.