Now, I'd like to put all these custom created redirects in a container (preferably within a list view) inside the main site - so the Content structure is this:
Content
Sample Site
Redirects
snippets
help
(all the other content ... )
The site node (Sample Site) has a Domain set (e.g. example.com)
The snippets and help documents are created with the Redirect doctype, but as you may have guessed, I'll need to go to example.com/redirects/snippets/ to be redirected to whatever I've specified; what I obviously want to have happen is that example.com/snippets/ triggers the redirect - I just want to have all the Redirect documents in a separate "folder" instead of in the root of the site...
Enter UrlProvider...
So I've created a UrlProvider for the Redirect doctype to essentially skip the /redirects/ part of the path, something like this:
public string GetUrl(UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode) {
var content = umbracoContext.ContentCache.GetById(id);
if (content != null && content.DocumentTypeAlias == "Redirect") {
// Redirects are stored below a `Redirects` node which shouldn't be part of the URL
return string.Format("/{0}/", content.UrlName);
}
return null;
}
This also works, as I can see on the properties page for a Redirect document, its URL is listed as e.g. "/snippets/". Yay!
But, alas, when browsing to the /snippets/ URL, I get a standard 404 document and no redirect...
What am I doing wrong? Do I need to return differently from the GetUrl() method and tell it to "keep handling URLs" or something?
I was thinking about that but I just figured it "should" work...
I don't really know the exact order of events in the request pipeline so it's very likely that I'll need to provide my own ContentFinder also, to make it work.
Luckily, I've just learned about them recently too, so I think I can figure that out - will update with my findings :-)
To follow up on Morten's answer, your content finder should look something like:
using System.Text.RegularExpressions;
using System.Web;
using Umbraco.Web;
using Umbraco.Web.Routing;
public class RedirectContetFinder : IContentFinder {
public bool TryFindContent(PublishedContentRequest request) {
// Get the raw URL (and do a little parsing)
string rawUrl = HttpContext.Current.Request.RawUrl.Split('?')[0].TrimEnd('/');
// Match the
Match match = Regex.Match(rawUrl, "^/([a-z]+)$");
if (match.Success) {
// Grab the "urlName" from the REGEX
string urlName = match.Groups[1].Value;
// Find the node in the content cache
request.PublishedContent = UmbracoContext.Current.ContentCache.GetByRoute("/redirects/" + urlName + "/");
}
// Return TRUE if we did find a matching node
return request.PublishedContent != null;
}
}
And then register it just before Umbraco's ContentFinderByNotFoundHandlers:
I may be thinking too much about this, but here's how I thought it all worked:
User saves document
Umbraco calls UrlProviders to generate document's URL
URL is stored for the document and viewable on the Properties tab
(time passes)
Browser requests URL
Umbraco's internal ContentFinders discover that the incoming request matches the aforementioned document's URL
Umbraco sees document has umbracoRedirect property and redirects the browser to actual location
The only thing that would make that break, would be if the URL (as returned by the UrlProvider) isn't actually stored on the document so Umbraco has to look it up every time it renders the document in the back office, right?
And I think that's exactly it. The UrlProvider is called whenever you call node.Url and is not a persisted property. The only thing that lives on the node is its urlName, which does not contain the path of its parents.
You could also just add the urlAlias (I thinks its called) to your redirect nodes, and type in the short url you want, and then let the UrlProvider write that url. But the there is yet another property for the editor to fill in (and screw up)
The URL provider is only for providing one or more visual URLs for the node - eg. the URLs shown in the backoffice, or when using the Url property on an IPublishedContent. The URL provider doesn't change the actual URL of the node.
So you can look at it like this:
An IUrlProvider affects the visual URL. Eg. what the user will see in the backoffice. This can be used so the user will see /snippets/ instead of /redirects/snippets/ in the backoffice, bot it won't change the actual URL.
An IContentFinder lets you provide a alternate or "virtual" URL for a given node. Eg. so the user can request /snippets/ instead of /redirects/snippets/.
I realize this isn't the approach Chriztian was after and I'm not contradicting anything said so far. I just want to propose an alternative approach that would be much easier if it meets the site goals.
As I understand it, there are pages deep in the site that need to have URLs that appear to be at the root of the site. I've had this situation in which marketing sends out emails and postcards with a vanity url such as example.com/sale but the actual url to the promotion might be at example.com/offers/current/april-fools-sale. I definitely don't want to litter the content tree by allowing promotions directly under the site's homepage for all kinds of reasons.
What I need is a way for content editors to make the pages and promotions where they belong in the content tree but also provide a simple way for them to enter an alternative URL to reach a page instantly.
Enter the umbracoUrlAlias property.
In my example, I would add a property to the Promotion document type with:
Name: Vanity URL
Alias: umbracoUrlAlias
Type: textfield
Description: [optional] type an additional url this page should answer to
In fact, you could type multiple url aliases separated with a comma. Such as sale,big-sale,april-fools-sale. You can even provide a full path if you wanted, such as sale,save/big,offers/april. But I don't bother content editors with those details because they always want a single top-level vanity url so why worry them with additional details they won't use.
Note that if you had a real page at /sale then a page with an umbracoUrlAlias of sale will never be seen. Real pages are served before looking for aliases.
The beauty of this solution is that you don't need a custom document type nor a redirection picker. Nor any custom code. Nor any IIS redirects. It's all built-in and obvious.
The down-side of this approach is that you will, in fact, have multiple urls for a single page which might be an SEO issue. It would depend on how it were used. For short-term promotions I'm not bothered by that potential. For a key page on the site it could be more of an issue. That is, if you ever link to the page by its .Url rather than using the .UmbracoUrlAlias property value.
However much I am on a never-ending quest for "the right solution for every client," where in this case their emphasis is very much on creating a short vanity URL that points to a page somewhere, I think I'll see if I can somehow change their perception this time to more like: Hey could this page be available as /easy-peasy/ when we send out emails?
And then they'd probably add easypeasy, eesypeesy and maybe even pez too, but they wouldn't have 4 Redirect nodes lying around (though the list view wouldn't mind that of course).
I think my main gripe with umbracoUrlAlias was always something along the lines of the SEO thing...
Using `umbracoRedirect` in conjunction with a UrlProvider doesn't work
Hi all,
I have a simple Redirect doctype with the "magic"
umbracoRedirect
property, so that I can create easy short URLs to content deep within the site.It's a Content Picker as described in the docs and it works great.
Now, I'd like to put all these custom created redirects in a container (preferably within a list view) inside the main site - so the Content structure is this:
The site node (Sample Site) has a Domain set (e.g. example.com)
The snippets and help documents are created with the Redirect doctype, but as you may have guessed, I'll need to go to example.com/redirects/snippets/ to be redirected to whatever I've specified; what I obviously want to have happen is that example.com/snippets/ triggers the redirect - I just want to have all the Redirect documents in a separate "folder" instead of in the root of the site...
Enter UrlProvider...
So I've created a
UrlProvider
for the Redirect doctype to essentially skip the /redirects/ part of the path, something like this:This also works, as I can see on the properties page for a Redirect document, its URL is listed as e.g. "/snippets/". Yay!
But, alas, when browsing to the /snippets/ URL, I get a standard 404 document and no redirect...
What am I doing wrong? Do I need to return differently from the
GetUrl()
method and tell it to "keep handling URLs" or something?/Chriztian
I think you also need a ContentFinder as the final part of your puzzle.
The UrlProvider writes the url, the ContentFinder resolves the url into a node.
Thanks Morten,
I was thinking about that but I just figured it "should" work...
I don't really know the exact order of events in the request pipeline so it's very likely that I'll need to provide my own ContentFinder also, to make it work.
Luckily, I've just learned about them recently too, so I think I can figure that out - will update with my findings :-)
To follow up on Morten's answer, your content finder should look something like:
And then register it just before Umbraco's
ContentFinderByNotFoundHandlers
:Disclaimer: The code is just a quick write some memory. It compiles in Visual Studio, but I haven't tested it further than that.
This blog might also help: http://24days.in/umbraco/2014/urlprovider-and-contentfinder/
Hi all - thanks for chippin' in!
I may be thinking too much about this, but here's how I thought it all worked:
(time passes)
umbracoRedirect
property and redirects the browser to actual locationThe only thing that would make that break, would be if the URL (as returned by the UrlProvider) isn't actually stored on the document so Umbraco has to look it up every time it renders the document in the back office, right?
And I think that's exactly it. The UrlProvider is called whenever you call node.Url and is not a persisted property. The only thing that lives on the node is its urlName, which does not contain the path of its parents.
Great - of course, then it makes perfect sense.
Dang it - a ContentFinder it is then - but I should just need to look through the Redirect nodes and see if a
urlName
matches...Should be possible :)
Thanks everyone!
/Chriztian
You could also just add the urlAlias (I thinks its called) to your redirect nodes, and type in the short url you want, and then let the UrlProvider write that url. But the there is yet another property for the editor to fill in (and screw up)
The URL provider is only for providing one or more visual URLs for the node - eg. the URLs shown in the backoffice, or when using the
Url
property on anIPublishedContent
. The URL provider doesn't change the actual URL of the node.So you can look at it like this:
An IUrlProvider affects the visual URL. Eg. what the user will see in the backoffice. This can be used so the user will see
/snippets/
instead of/redirects/snippets/
in the backoffice, bot it won't change the actual URL.An IContentFinder lets you provide a alternate or "virtual" URL for a given node. Eg. so the user can request
/snippets/
instead of/redirects/snippets/
.Good wrap-up of how they work together. Thanks!
I realize this isn't the approach Chriztian was after and I'm not contradicting anything said so far. I just want to propose an alternative approach that would be much easier if it meets the site goals.
As I understand it, there are pages deep in the site that need to have URLs that appear to be at the root of the site. I've had this situation in which marketing sends out emails and postcards with a vanity url such as
example.com/sale
but the actual url to the promotion might be atexample.com/offers/current/april-fools-sale
. I definitely don't want to litter the content tree by allowing promotions directly under the site's homepage for all kinds of reasons.What I need is a way for content editors to make the pages and promotions where they belong in the content tree but also provide a simple way for them to enter an alternative URL to reach a page instantly.
Enter the
umbracoUrlAlias
property.In my example, I would add a property to the Promotion document type with:
In fact, you could type multiple url aliases separated with a comma. Such as
sale,big-sale,april-fools-sale
. You can even provide a full path if you wanted, such assale,save/big,offers/april
. But I don't bother content editors with those details because they always want a single top-level vanity url so why worry them with additional details they won't use.Note that if you had a real page at
/sale
then a page with an umbracoUrlAlias ofsale
will never be seen. Real pages are served before looking for aliases.The beauty of this solution is that you don't need a custom document type nor a redirection picker. Nor any custom code. Nor any IIS redirects. It's all built-in and obvious.
The down-side of this approach is that you will, in fact, have multiple urls for a single page which might be an SEO issue. It would depend on how it were used. For short-term promotions I'm not bothered by that potential. For a key page on the site it could be more of an issue. That is, if you ever link to the page by its .Url rather than using the .UmbracoUrlAlias property value.
cheers,
doug.
Thanks for your input Doug,
However much I am on a never-ending quest for "the right solution for every client," where in this case their emphasis is very much on creating a short vanity URL that points to a page somewhere, I think I'll see if I can somehow change their perception this time to more like: Hey could this page be available as /easy-peasy/ when we send out emails?
And then they'd probably add easypeasy, eesypeesy and maybe even pez too, but they wouldn't have 4 Redirect nodes lying around (though the list view wouldn't mind that of course).
I think my main gripe with
umbracoUrlAlias
was always something along the lines of the SEO thing.../Chriztian
is working on a reply...