PDF Document Handling as Content Elements, not Media
I'm in the process of implementing Umbraco 4.7.1 for a content heavy website (~7,000-10,000 static pages). Due to the nature of the website, a large number of "pages" are PDFs rathern than HTML (gotta love it). As a result, we'd like to allow content owners to view their PDF content along side of their HTML content in the content tree. We'd also like to ensure that PDF content urls maintain a friendly url structure similar to the content it sits near in the tree, rather than managing them within the Media section.
Essentially if we have ContentElement1 and PdfElement1, both children of ParentElement1, the urls should be:
To accomplish this I've created a PDF document type that includes an Upload property that will house the actual file. I've also added a Template that will act as a conduit for requests, grabbing the current node's property information and determining the file to pass to the user. If you navigate to this url now, a Response.TransmitFile() occurs along with proper header information to directly stream the PDF to the user based on their request. The final step was to then configure the URLRewrite to handle ~/(.*).pdf and hand it off to ~/$1/.
Everything is working as I want it to at this point, but I'm at my final stage and would greatly appreciate a little help.
Since we've set umbracoUseDirectoryUrls to true, it seems there is no way to override the link to document to force the .pdf extension I'd prefer. I've attempted umbracoUrlAlias, but that doesn't seem to work. Any ideas on how I may be able to handle this aspect of what I'm trying to accomplish?
What you've done seems fine, but I don't think there's an easy way to get umbraco to append the PDF file extension to the end of the URLs, as by default you're limited to either extensionless /pagename or .aspx /pagename.aspx.
You could make your own version of NiceUrl to render out PDF document types with the .pdf on the end of the URL and use that in your macros instead of the umbraco.library.NiceUrl one, but you'd have problems with any links to the PDFs that are linked to in the Richtext editor, as the links are replaced somewhere that you can't override so those links would not get the .PDF extension added to them.
Thanks for the response. I actually think I have a pretty good and workable solution right now. I'm new around here, and not sure how the contribution of features back into the code works, but I might see if it is worth submitting. Here's what I did in case anyone else would like static content files (like PDFs) to show up in the content tree along with other content elements:
2. Create a new Template (I called ours StaticDocument) and set Inherits="Core.Display.Umbraco.DocumentStream" Set the various requirement content placeholders, etc, that you need
3. Create a new set of DocumentTypes, one for each static file type you'd like to accomodate. (You could do just 1 DocumentType, but I did multiple to allow different icons for each type in the tree). Make this DocumentType use the default template of StaticDocument. For this first example, call it PDFDocument (or at least makt this the alias).
4. In the PDFDocument properties, add the following two: File (staticFile), Type: Upload File Extension (umbracoFileExtension), Type: Label
5. Create a .cs file that you will place in the App_Code folder with the following content
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.UI; using System.Web; using System.IO; using umbraco.NodeFactory; using umbraco.interfaces;
namespace Core.Display.Umbraco {
public class DocumentStream : MasterPageTemplate { public DocumentStream(){ this.Load += new EventHandler(DocumentStreamLoad); }
private void DocumentStreamLoad(object sender, EventArgs e){ Node currentNode = ((UmbracoBasePage) this.Page).CurrentNode; string extension = currentNode.Properties["umbracoFileExtension"].Value; if (Request.RawUrl.Split('?')[0].Split('#')[0].ToLower().EndsWith(extension.ToLower())){ //This is a call to a GetContentType Method we have, //see this URL for a non proprietary solution //http://stackoverflow.com/questions/58510/using-net-how-can-you-find-the-mime-type-of-a-file-based-on-the-file-signature Response.ContentType = ModuleConfig.GetContentType(extension); Response.TransmitFile(Server.MapPath(currentNode.Properties["staticFile"].Value)); Response.End(); } Response.Write("Need to do some work to throw an actual 404 but wanted to get this solution up."); Response.End(); } } }
6. Make adjustments to the umbraco.presentation.library.cs file's NiceUrlJuno, NiceUrlFetch, and appendUrlExtension methods with the following
if (node == null) { ArgumentException arEx = new ArgumentException( string.Format( "Couldn't find any page with the nodeId = {0}. This is most likely caused by the page isn't published!", nodeId), "nodeId"); Log.Add(LogTypes.Error, nodeId, arEx.Message); throw arEx; } if (node.ParentNode.Name.ToLower() != "root" || UmbracoSettings.UseDomainPrefixes || forceDomain) { if (UmbracoSettings.UseDomainPrefixes || forceDomain) { Domain[] domains = Domain.GetDomainsById(nodeId); // when there's a domain on a url we'll just return the domain rather than the parent path if (domains.Length > 0) { return GetDomainIfExists(currentDomain, nodeId); } }
if (parentUrl == String.Empty && (int.Parse(node.Attributes.GetNamedItem("level").Value) > startNodeDepth || UmbracoSettings.UseDomainPrefixes || forceDomain)) { if (node.ParentNode.Name != "root") { parentUrl = niceUrlJuno(int.Parse(node.ParentNode.Attributes.GetNamedItem("id").Value), startNodeDepth, currentDomain, forceDomain, out nodeExtension); } } } // only return the current node url if we're at the startnodedepth or higher if (int.Parse(node.Attributes.GetNamedItem("level").Value) >= startNodeDepth) { if (node.SelectSingleNode("umbracoFileExtension") != null && !string.IsNullOrEmpty(node.SelectSingleNode("umbracoFileExtension").InnerText)) nodeExtension = node.SelectSingleNode("umbracoFileExtension").InnerText; else nodeExtension = ".aspx"; return parentUrl + "/" + node.Attributes.GetNamedItem("urlName").Value; } else if (node.PreviousSibling != null) return "/" + node.Attributes.GetNamedItem("urlName").Value; else return "/"; } internal static string NiceUrlFetch(int nodeID, int startNodeDepth, bool forceDomain) { bool directoryUrls = GlobalSettings.UseDirectoryUrls; string baseUrl = SystemDirectories.Root; // SystemDirectories.Umbraco; string nodeExtension; string junoUrl = niceUrlJuno(nodeID, startNodeDepth, HttpContext.Current.Request.ServerVariables["SERVER_NAME"].ToLower(), forceDomain, out nodeExtension); return appendUrlExtension(baseUrl, directoryUrls, junoUrl, nodeExtension);
}
private static string appendUrlExtension(string baseUrl, bool directoryUrls, string tempUrl, string extensionToAppend) { if (!directoryUrls) { // append .aspx extension if the url includes other than just the domain name if (!String.IsNullOrEmpty(tempUrl) && tempUrl != "/" && (!tempUrl.StartsWith("http://") || tempUrl.LastIndexOf("/") > 7)) tempUrl = baseUrl + tempUrl + extensionToAppend; } else { tempUrl = baseUrl + tempUrl; if (extensionToAppend != ".aspx") { tempUrl += extensionToAppend; } else if (tempUrl != "/" && UmbracoSettings.AddTrailingSlash) { tempUrl += "/"; } } return tempUrl; }
7. Finally, add the default value of file extension to the content element on save based on the document type that is selected. Create a DefaultValues.cs file and drop it in your App_Code directory that looks like this.
using System; using System.Collections.Generic; using System.Text; using umbraco.cms.businesslogic.web; using umbraco.cms.businesslogic; using umbraco.BusinessLogic;
namespace Core.Display.Umbraco { public class DefaultValues : umbraco.BusinessLogic.ApplicationBase { public DefaultValues() { Document.BeforeSave += new Document.SaveEventHandler(Document_BeforeSave); } void Document_BeforeSave(Document sender, SaveEventArgs e) {
8. Create your PDF (or whatever) and publish it. You'll notice the URL is what you want it to be, LocalLink resolves to the correct location, and all is well in the world.
There may be a 1000% easier solution that what I've done, but forgive me, I'm an Umbraco n00b, and this works really well for our purposes. The next step will be to find a control, or create one on our own, that allows upload only based on file extension. I could also see default values of labels being easier than this, perhaps with a hidden label default value data type or something. Hope this is helpful to someone.
PDF Document Handling as Content Elements, not Media
I'm in the process of implementing Umbraco 4.7.1 for a content heavy website (~7,000-10,000 static pages). Due to the nature of the website, a large number of "pages" are PDFs rathern than HTML (gotta love it). As a result, we'd like to allow content owners to view their PDF content along side of their HTML content in the content tree. We'd also like to ensure that PDF content urls maintain a friendly url structure similar to the content it sits near in the tree, rather than managing them within the Media section.
Essentially if we have ContentElement1 and PdfElement1, both children of ParentElement1, the urls should be:
http://domain.com/ParentElement1/
http://domain.com/ParentElement1/ContentElement1/
http://domain.com/ParentElement1/PdfElement1.pdf
To accomplish this I've created a PDF document type that includes an Upload property that will house the actual file. I've also added a Template that will act as a conduit for requests, grabbing the current node's property information and determining the file to pass to the user. If you navigate to this url now, a Response.TransmitFile() occurs along with proper header information to directly stream the PDF to the user based on their request. The final step was to then configure the URLRewrite to handle ~/(.*).pdf and hand it off to ~/$1/.
Everything is working as I want it to at this point, but I'm at my final stage and would greatly appreciate a little help.
Since we've set umbracoUseDirectoryUrls to true, it seems there is no way to override the link to document to force the .pdf extension I'd prefer. I've attempted umbracoUrlAlias, but that doesn't seem to work. Any ideas on how I may be able to handle this aspect of what I'm trying to accomplish?
Any help is greatly appreciated.
Hiya,
What you've done seems fine, but I don't think there's an easy way to get umbraco to append the PDF file extension to the end of the URLs, as by default you're limited to either extensionless /pagename or .aspx /pagename.aspx.
You could make your own version of NiceUrl to render out PDF document types with the .pdf on the end of the URL and use that in your macros instead of the umbraco.library.NiceUrl one, but you'd have problems with any links to the PDFs that are linked to in the Richtext editor, as the links are replaced somewhere that you can't override so those links would not get the .PDF extension added to them.
Tim,
Thanks for the response. I actually think I have a pretty good and workable solution right now. I'm new around here, and not sure how the contribution of features back into the code works, but I might see if it is worth submitting. Here's what I did in case anyone else would like static content files (like PDFs) to show up in the content tree along with other content elements:
1. Update Url Rewrite
2. Create a new Template (I called ours StaticDocument) and set Inherits="Core.Display.Umbraco.DocumentStream"
Set the various requirement content placeholders, etc, that you need
3. Create a new set of DocumentTypes, one for each static file type you'd like to accomodate. (You could do just 1 DocumentType, but I did multiple to allow different icons for each type in the tree). Make this DocumentType use the default template of StaticDocument. For this first example, call it PDFDocument (or at least makt this the alias).
4. In the PDFDocument properties, add the following two:
File (staticFile), Type: Upload
File Extension (umbracoFileExtension), Type: Label
5. Create a .cs file that you will place in the App_Code folder with the following content
6. Make adjustments to the umbraco.presentation.library.cs file's NiceUrlJuno, NiceUrlFetch, and appendUrlExtension methods with the following
7. Finally, add the default value of file extension to the content element on save based on the document type that is selected. Create a DefaultValues.cs file and drop it in your App_Code directory that looks like this.
8. Create your PDF (or whatever) and publish it. You'll notice the URL is what you want it to be, LocalLink resolves to the correct location, and all is well in the world.
There may be a 1000% easier solution that what I've done, but forgive me, I'm an Umbraco n00b, and this works really well for our purposes. The next step will be to find a control, or create one on our own, that allows upload only based on file extension. I could also see default values of labels being easier than this, perhaps with a hidden label default value data type or something. Hope this is helpful to someone.
Gah, I took so much time on proper formatting and tabbing of my reply...oh well.
is working on a reply...