301 redirect module fails when using custom url provider
I developed a custom URL provider which cuts off the parent UrlName from the URL making the URL appear like it's located on the website root.
For example : /profile/testnode becomes /testnode
Now I was testing renaming this node and saw that Umbraco made a redirect from the non-customized URL to the new URL. See the image below.
I expected to see a redirect from the customized url to the new url. Is this an error in the way Umbraco handles redirecting URL’s? Umbraco does show the correct url on the properties tab and the url is working correctly too. It’s just not using the right url for the redirect after renaming.
Still don’t have a good solution. Even manually adding the right redirects via the code API doesn’t work. I would expect there to be some kind of builtin solution.
Thanks for your post. I knew about that package. But I expected Umbraco to automatically make the right redirects and it doesn't. Even when renaming a node it picks the wrong URL because I use a custom URL provider. So for me it's completely useless.
I am looking for an automated solution, I don't want to add redirects by hand.
The Redirect Url Manager is quite a simplistic implementation, that I don't think is designed to handle every scenario 'yet'- and this sounds like an issue in the way the Redirect Url Manager determines what the 'actual' published url of the page was, when a change is made...
The redirects are created in the RedirectTrackingEventHandler
private static void CreateRedirect(int contentId, Guid contentKey, string oldRoute)
{
var contentCache = GetPublishedCache();
if (contentCache == null) return;
var newRoute = contentCache.GetRouteById(contentId);
if (IsNotRoute(newRoute) || oldRoute == newRoute) return;
var redirectUrlService = ApplicationContext.Current.Services.RedirectUrlService;
redirectUrlService.Register(oldRoute, contentKey);
}
This event is triggered when the cache is updated:
private void PageCacheRefresher_CacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs cacheRefresherEventArgs)
{
// only on master / single, not on replicas!
if (IsReplicaServer) return;
// simply getting OldRoutes will register it in the request cache,
// so whatever we do with it, try/finally it to ensure it's cleared
try
{
foreach (var oldRoute in OldRoutes)
{
// assuming we cannot have 'CacheUpdated' for only part of the infos else we'd need
// to set a flag in 'Published' to indicate which entities have been refreshed ok
CreateRedirect(oldRoute.Key, oldRoute.Value.Item1, oldRoute.Value.Item2);
}
}
finally
{
OldRoutes.Clear();
RequestCache.ClearCacheItem(ContextKey3);
}
}
The OldRoutes property is a dictionary containing the routes of an item through the publishing process,
var oldRoutes = RequestCache.GetCacheItem<>
this collection of 'OldRoutes' is populated during the ContentService_Publishing event: (eg before the published urls change, you need to know what they once were!)
foreach (var entity in entities)
{
// for each entity, we want to save the 'old route' of any impacted entity.
//
// previously, we'd save the routes of all descendants - super safe but has an
// impact on perfs - assuming that the descendant routes will NOT change if the
// entity's segment does not change (else... outside of the scope of the simple,
// built -in, tracker) then we can compare the entity's old and new segments
// and avoid processing the descendants
var process = true;
if (Moving == false) // always process descendants when moving
{
// SD: in 7.5.0 we re-lookup the entity that is published, which gets its
// current state in the DB, which we use to get the 'old' segment. In the
// future this will certainly cause some problems, to fix this we'd need to
// change the IUrlSegmentProvider to support being able to determine if a
// segment is going to change for an entity. See notes in IUrlSegmentProvider.
var oldEntity = ApplicationContext.Current.Services.ContentService.GetById(entity.Id);
if (oldEntity == null) continue;
var oldSegment = oldEntity.GetUrlSegment();
var newSegment = entity.GetUrlSegment();
process = oldSegment != newSegment;
}
// skip if no segment change
if (process == false) continue;
// else save routes for all descendants
var entityContent = contentCache.GetById(entity.Id);
if (entityContent == null) continue;
foreach (var x in entityContent.DescendantsOrSelf())
{
var route = contentCache.GetRouteById(x.Id);
if (IsNotRoute(route)) continue;
var wk = UnwrapToKey(x);
if (wk == null) continue;
OldRoutes[x.Id] = Tuple.Create(wk.Key, route);
}
}
So it is using the UrlSegmentProvider implementation to detect the change in url segment for a node that would trigger the creation of redirects, and populates the 'OldRoutes' dictionary once publishing is confirmed, (and the cache is updated to avoid creating redirects when publishing fails), it then creates the redirects - but it isn't, I don't think considering any CustomUrlProvider implementation...
So I would suggest raising it as an issue so it can be discussed as the best way to handle this scenario, or in case I've misunderstood!
In the meantime, 'you could' perform the same trick / use the same pattern of creating your own handling of the ContentServicePublishing Event, determine if a Url will change based on your CustomUrlProvider logic, and then triggering the creation of the Redirect by your own handling of the PageCacheRefresherCacheUpdated event
Thank you for your extensive analysis. I considered implementing my own event handler but didn't like having 2 redirects created for the same page. But I don't think there's any other solution for now so that will probably be my approach.
I did not raise it as an issue on the issue tracker yet. I will do as soon as I can. Would be nice to see this issue fixed.
301 redirect module fails when using custom url provider
I developed a custom URL provider which cuts off the parent UrlName from the URL making the URL appear like it's located on the website root.
For example :
/profile/testnode
becomes/testnode
Now I was testing renaming this node and saw that Umbraco made a redirect from the non-customized URL to the new URL. See the image below.
I expected to see a redirect from the customized url to the new url. Is this an error in the way Umbraco handles redirecting URL’s? Umbraco does show the correct url on the properties tab and the url is working correctly too. It’s just not using the right url for the redirect after renaming.
Still don't have a solution for this issue.
Still don’t have a good solution. Even manually adding the right redirects via the code API doesn’t work. I would expect there to be some kind of builtin solution.
Hey Bernard,
What I found in my previous project is that built-in redirect only works when you rename the page, and you can't edit those entries.
However, there are plugin that you can use todo the 301 redirect, and they were quite simple to use.
Simple 301 https://our.umbraco.com/packages/backoffice-extensions/simple-301/
301 URL Tracker https://our.umbraco.com/packages/developer-tools/301-url-tracker/
Both of them will give you another tab in the content section, just like below image
Thanks for your post. I knew about that package. But I expected Umbraco to automatically make the right redirects and it doesn't. Even when renaming a node it picks the wrong URL because I use a custom URL provider. So for me it's completely useless.
I am looking for an automated solution, I don't want to add redirects by hand.
Waiting
Hi Bernard
Have you raised this on the Issue Tracker?
https://github.com/umbraco/Umbraco-CMS/issues
The Redirect Url Manager is quite a simplistic implementation, that I don't think is designed to handle every scenario 'yet'- and this sounds like an issue in the way the Redirect Url Manager determines what the 'actual' published url of the page was, when a change is made...
The redirects are created in the RedirectTrackingEventHandler
https://github.com/umbraco/Umbraco-CMS/blob/2f74720c38e734f359cdd3e9a7556860fb96801f/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs
This event is triggered when the cache is updated:
private void PageCacheRefresher_CacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs cacheRefresherEventArgs) { // only on master / single, not on replicas! if (IsReplicaServer) return;
The OldRoutes property is a dictionary containing the routes of an item through the publishing process,
var oldRoutes = RequestCache.GetCacheItem<>
this collection of 'OldRoutes' is populated during the ContentService_Publishing event: (eg before the published urls change, you need to know what they once were!)
So it is using the UrlSegmentProvider implementation to detect the change in url segment for a node that would trigger the creation of redirects, and populates the 'OldRoutes' dictionary once publishing is confirmed, (and the cache is updated to avoid creating redirects when publishing fails), it then creates the redirects - but it isn't, I don't think considering any CustomUrlProvider implementation...
So I would suggest raising it as an issue so it can be discussed as the best way to handle this scenario, or in case I've misunderstood!
In the meantime, 'you could' perform the same trick / use the same pattern of creating your own handling of the ContentServicePublishing Event, determine if a Url will change based on your CustomUrlProvider logic, and then triggering the creation of the Redirect by your own handling of the PageCacheRefresherCacheUpdated event
regards
Marc
Thank you for your extensive analysis. I considered implementing my own event handler but didn't like having 2 redirects created for the same page. But I don't think there's any other solution for now so that will probably be my approach.
I did not raise it as an issue on the issue tracker yet. I will do as soon as I can. Would be nice to see this issue fixed.
@Marc Goodson https://github.com/umbraco/Umbraco-CMS/issues/3186
It's a shame that this issue is still open and my pull request isn't being looked at.
https://github.com/umbraco/Umbraco-CMS/pull/3837
It's been open for almost 2 months. What more can I do
is working on a reply...