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...
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.
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.
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"
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(); } }
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.
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
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:
My custom route segment:
And then I hook it up in Global.asax.cs:
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.