I am using the UrlProvider together with the IContentFinder to setup a custom url for my project.
So I have a structure like:
Home
Categories
Categorie-1
Sub-categorie-1
Sub-categorie-2
Categorie-2
Sub-categorie-3
Sub-categorie-4
Using the UrlProvider and ContentFinder I now have an url /categorie-1/ and /sub-categorie-1/ which then shows the correct template.
But now with Url tracker enabled, when I change categorie Categorie-1 to Categorie-6 it adds the full orignal url eg /categories/categorie-1/ in the url tracker to be redirected and not the /categorie-1/.
In my experience with the CustomUrlProvider, that just tells Umbraco what the URL should be; it is still saving the full url for the node. One option would be to not only use the Provider, but also save the URL in the property umbracoUrlAlias, which will override the default URL generated by Umbraco. Not sure if URL Tracker supports this, but it would be my next step in troubleshooting.
umbracoUrlAlias can be very useful, but keep in mind that it adds an alias.
Since the original url also remains valid, Umbraco won't add a redirect to the Redirect URL Management.
>
Concerning the contentprovider/urlprovider behaviour for registering redirects for old urls:
I am not sure if it can be classified as a bug.
But Umbraco just doesn't use the information provided by the urlprovider which sounds rather strange and restricting.
For the current (=old) url, Umbraco is using GetRouteById for the descendants of the content entity (in RedirectTrackingEventHandler.cs, ContentService_Publishing) to get the orignal route:
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);
}
This way, it generates a route that completely ignores the urlproviders url.
There's probably more needed to deal with all possible situations, but at this moment it is not clear to me why the code for the ContentService_Publishing method does not simply use the urlprovider (as in the GetContentUrls method in UrlProviderExtensions.cs)?
Hopefully someone of the Umbraco dev team can give some insight in the reason why it is implemented this way.
Is there a neat way to override this behaviour (eg. by using a custom UrlSegmentProvider)?
It would also be interesting to know if something like:
// Find route using the urlprovider
string url;
bool routeFound = false;
var urlProvider = UmbracoContext.Current.RoutingContext.UrlProvider;
try
{
url = urlProvider.GetUrl(entityContent.Id).TrimEnd(new[] { '/' });
if (!IsNotRoute(url))
{
var wk = UnwrapToKey(entityContent);
if (wk != null)
{
OldRoutes[entityContent.Id] = Tuple.Create(wk.Key, url);
routeFound = true;
}
}
}
catch
{
//
}
if (!routeFound)
{
// the original code continues here ...
foreach (var x in entityContent.DescendantsOrSelf())
{
..
}
}
could work for ContentService_Publishing?
Out of curiousity (and certainly not for production), I made a quick and dirty change in the Umbraco source code using this example to see what happens.
For a local test situation with a similar Company urlprovider Michaël is using, it works fine. Changing a Company node, gives the right redirect path.
Add new url to the umbracoUrlAlias ( just need to find a way to do this automatically, maybe hook in into the content save and publish event and then copy this new url to here... )
Add url redirect with regex in IIS to do the redirect from the current ( old ) url to the new one ( won't be that hard because the structure is always the same )
But like you are mentioning, it would be nicer that Umbraco uses the provided custom UrlProvider to get the correct url for a node.
If you are going to hook into the save and publish event, you could (instead of the two steps you mentioned) try to add your own redirects just like the CreateRedirect method in RedirectTrackingEventHandler.cs.
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);
}
Have not tried it myself yet, but if you use the urlProvider.GetUrl and assign it to oldRoute in that code, I think it is worth trying.
With some help from the original Umbraco source code (RedirectTrackingEventHandler.cs), the following code will give a nice start:
public class CustomEventHandler : ApplicationEventHandler
{
private static IPublishedContentWithKey UnwrapToKey(IPublishedContent content)
{
if (content == null) return null;
var withKey = content as IPublishedContentWithKey;
if (withKey != null) return withKey;
var extended = content as PublishedContentExtended;
while (extended != null)
extended = (content = extended.Unwrap()) as PublishedContentExtended;
withKey = content as IPublishedContentWithKey;
return withKey;
}
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Saving += ContentService_Saving;
// Use custom Url provider
UrlProviderResolver.Current.InsertTypeBefore<DefaultUrlProvider, CompanyUrlProvider>();
// Insert custom finder before ContentFinderByNiceUrl or ContentFinderByNotFoundHandlers
ContentFinderResolver.Current.InsertTypeBefore<ContentFinderByNiceUrl, CompanyContentFinder>();
}
private void ContentService_Saving(IContentService sender, SaveEventArgs<IContent> e)
{
// Register redirections using UrlProvider
var contentCache = UmbracoContext.Current.ContentCache;
var urlProvider = UmbracoContext.Current.RoutingContext.UrlProvider;
var redirectUrlService = ApplicationContext.Current.Services.RedirectUrlService;
if (contentCache != null && urlProvider != null && redirectUrlService != null)
{
foreach (IContent entity in e.SavedEntities)
{
var oldEntity = ApplicationContext.Current.Services.ContentService.GetById(entity.Id);
if (oldEntity == null) continue;
var oldSegment = oldEntity.GetUrlSegment();
var newSegment = entity.GetUrlSegment();
if (oldSegment != newSegment)
{
// Segment changed; redirection needed.
var entityContent = contentCache.GetById(entity.Id);
// Get the old url via the UrlProvider
var oldRoute = urlProvider.GetUrl(entityContent.Id).TrimEnd(new[] { '/' });
var wk = UnwrapToKey(entityContent);
if (wk == null) continue;
// Register the oldroute
redirectUrlService.Register(oldRoute, wk.Key);
}
}
}
//else
//{
// If no contentCache, urlProvider or redirectUrlService available,
// leave it up to the default redirection handled by the RedirectTrackingEventhandler
//}
//Clear the content finder cache.
HttpContext.Current.Cache.Remove("CachedCompanyNodes");
}
}
Changing a published Company node (under a Companies node) with the name "Company X" to "Company X1" will register the following redirects:
/company-x/ ==> /company-x1/
/companies/company-x/ ==> /company-x1/
The code needs some refinement and further testing, but the idea to register redirects using the urlProvider looks already pretty good.
Note that with this approach, the default redirection will still be executed and will result in an extra (but not completely right) redirect item in the list.
Although the original url of that item is not comform the url provided by the custom urlProvider, it might actually be very handy to have such a redirection as a bonus ;-)
If anyone has ideas to improve it ... please do not hesitate to share your ideas and code!
Url tracker issue with custom UrlProvider
Hi all,
I am using the
UrlProvider
together with theIContentFinder
to setup a custom url for my project.So I have a structure like:
Using the UrlProvider and ContentFinder I now have an url
/categorie-1/
and/sub-categorie-1/
which then shows the correct template.But now with Url tracker enabled, when I change categorie
Categorie-1
toCategorie-6
it adds the full orignal url eg/categories/categorie-1/
in the url tracker to be redirected and not the/categorie-1/
.Can I solve this?
/Michaël
In my experience with the CustomUrlProvider, that just tells Umbraco what the URL should be; it is still saving the full url for the node. One option would be to not only use the Provider, but also save the URL in the property umbracoUrlAlias, which will override the default URL generated by Umbraco. Not sure if URL Tracker supports this, but it would be my next step in troubleshooting.
umbracoUrlAlias can be very useful, but keep in mind that it adds an alias.
Since the original url also remains valid, Umbraco won't add a redirect to the Redirect URL Management.
>
Concerning the contentprovider/urlprovider behaviour for registering redirects for old urls:
I am not sure if it can be classified as a bug. But Umbraco just doesn't use the information provided by the urlprovider which sounds rather strange and restricting.
For the current (=old) url, Umbraco is using GetRouteById for the descendants of the content entity (in RedirectTrackingEventHandler.cs, ContentService_Publishing) to get the orignal route:
This way, it generates a route that completely ignores the urlproviders url.
There's probably more needed to deal with all possible situations, but at this moment it is not clear to me why the code for the ContentService_Publishing method does not simply use the urlprovider (as in the GetContentUrls method in UrlProviderExtensions.cs)?
Hopefully someone of the Umbraco dev team can give some insight in the reason why it is implemented this way. Is there a neat way to override this behaviour (eg. by using a custom UrlSegmentProvider)?
It would also be interesting to know if something like:
could work for ContentService_Publishing?
Out of curiousity (and certainly not for production), I made a quick and dirty change in the Umbraco source code using this example to see what happens. For a local test situation with a similar Company urlprovider Michaël is using, it works fine. Changing a Company node, gives the right redirect path.
Hi Micha,
thank you for pointing this out!
What I had in mind was:
But like you are mentioning, it would be nicer that Umbraco uses the provided custom UrlProvider to get the correct url for a node.
/Michaël
Hi Michaël,
Yes, I think that might work.
If you are going to hook into the save and publish event, you could (instead of the two steps you mentioned) try to add your own redirects just like the CreateRedirect method in RedirectTrackingEventHandler.cs.
Have not tried it myself yet, but if you use the urlProvider.GetUrl and assign it to oldRoute in that code, I think it is worth trying.
Hi Micha,
nice one!
I will test this out and let you know what the results are.
Wonderfull to have such a nice community where you learn more and more each day!
Kind regards
/Michaël
Hi Michaël,
With some help from the original Umbraco source code (RedirectTrackingEventHandler.cs), the following code will give a nice start:
Changing a published Company node (under a Companies node) with the name "Company X" to "Company X1" will register the following redirects:
The code needs some refinement and further testing, but the idea to register redirects using the urlProvider looks already pretty good.
Note that with this approach, the default redirection will still be executed and will result in an extra (but not completely right) redirect item in the list. Although the original url of that item is not comform the url provided by the custom urlProvider, it might actually be very handy to have such a redirection as a bonus ;-)
If anyone has ideas to improve it ... please do not hesitate to share your ideas and code!
/Micha
is working on a reply...