Friendly urls to media archive files

Topics: General
May 9, 2011 at 8:24 PM

Hello there

I am building a new website with Composite C1.

we currently have about 110 PDF documents (technical papers, white papers) on our website.

with Composite C1 and without Issuu player,  I would like serve these PDFs under links such as

www.domainname.com/papers/tech-papers/document-1.pdf

........…………………………………....................................../document-2.pdf

.......………………………………........................................../document-3.pdf          

instead of www.domainname.com/Renderers/ShowMedia.ashx?id=548ur48rfj101928283

(like creating a folder or a container for the PDFs)

 

How can I accomplish this?  is this possible?

 

I hope my question is clear enough.

Thanks in advance for the help

 

Maydin

May 10, 2011 at 9:38 AM

Maydin,
Idea is write http module to manage pdf urls.

I suggest quick & dirty solution based on existing package Composite.Tools.LegacyUrlHandler package. 

1. Install package
2. Kill Composite.Tools.LegacyUrlHandler.dll from the bin, remove StoreCurrentPaths.aspx and RemoveRedunantPaths.aspx from the root
3. Copy LegacyUrlHttpModule.cs, LegacyUrlHandlerFacade.cs and Cache.cs to the App_Code (please find them here - http://c1packages.codeplex.com/SourceControl/changeset/view/7280# - click 2 times Composite.Tools.LegacyUrlHandler - you will find files inside) 
4. Edit LegacyUrlHttpModule.cs and change private const string PathInfoToken = ".pdf";
5. Create /App_Data/LegacyUrlMappings.xml
6. Put inside:

<Mappings>   <Mapping OldPath="/papers/tech-papers/document-1.pdf" NewPath="~/Renderers/ShowMedia.ashx?id=32a3f59c-1d86-4a6f-8acb-d7dcb049fb21" /> </Mappings>

where OldPath is desired url and NewPath - your actual media url.

 



 

Coordinator
May 10, 2011 at 9:54 AM
Edited May 10, 2011 at 9:56 AM

@aeont does it mean that in your solution all the document names have to be present in web.config?

I suggest using UrlRewriting 

ShowMedia.ashx can accept path to C1's media archive file as a parameter, urls looks like

~/Renderers/ShowMedia.ashx?id=MediaArchive:/Pdfs/document-1.pdf

So what you need is to add a replacement:

 

<rewrite url="~/papers/tech-papers/" to="~/Renderers/ShowMedia.ashx?id=MediaArchive:/papers/tech-papers/" />

 

P.S. I renamed the discussion to "Friendly urls to media archive files"

May 10, 2011 at 10:54 AM
Edited May 10, 2011 at 10:55 AM

@napernik,
yes, in this sample all documents have to be listed in the /App_Data/LegacyUrlMappings.xml
your idea, of course, is more elegant and could be implemented based on Composite.Tools.LegacyUrlHandler package code as well.

May 10, 2011 at 5:59 PM

Hello aeont and napernik

Thank you both. I appreciate

@napernik   the url is looks like

~/Renderers/ShowMedia.ashx?id=32a3f59c-1d86-4a6f-8acb-d7dcb049fb21  

NOT  ~/Renderers/ShowMedia.ashx?id=MediaArchive:/Pdfs/document-1.pdf 

Am I wrong? 

My question is how do i get such a url  so i can add a replacement to it

Thanks

Maydin

 

Coordinator
May 10, 2011 at 8:28 PM
Edited May 10, 2011 at 8:29 PM

>> Am I wrong? 

Both media urls formats are supported. The variant with a GUID is usually more prefereble, since in the case file or it's folder is renamed the url will still be the same.

>> My question is how do i get such a url  so i can add a replacement to it

All the media files are represented as Composite.Data.Types.IMediaFile interface, it has a property called "CompositePath" which is basically "MediaArchive:" + {path to a media file}.

So in C# your code will look like

 

static string GetUrl(IMediaFile file) {
	return "/Renderers/ShowMedia.ashx?id=" + file.CompositePath;
}

If you have your pdf link in only one place, you can just create an xslt function that will build those urls.

Or, maybe you would like to have a general solution, that will be fixing all the links by itself. In this case you may use method Composite.Core.WebClient.PageUrlHelper.ChangeRenderingPageUrlsToPublic(...) as an inspiration, 
write a method that searches for all the references like
 /Renderers/ShowMedia.ashx?id={media file id}, 
and replaces it with 
 /Renderers/ShowMedia.ashx?id={CompositePath}

Then call this method in /Renderers/Page.aspx.cs file after line
 xhtml = PageUrlHelper.ChangeRenderingPageUrlsToPublic(markupBuilder.ToString());
Once you are sure it's working, you can replace some of those urls with your own format and use UrlFormatting to redirect it to ShowMedia.ashx
P.S. In upcoming version's we're planning to have seo-friendly media urls. It would be something like www.domainname.com/{Guid}/{Folder}/document-1.pdf
May 11, 2011 at 12:26 AM

Hi napernik

unfortunately i am just a newbee -I guess you already got that :))  I wish i could implement your suggestions

i placed  the code 

static string GetUrl(IMediaFile file) {
return "/Renderers/ShowMedia.ashx?id=" + file.CompositePath;
}

onto ShowMedia.ashx page, but i cannot see the "CompositePath". 

i know you did explain it,  but  i am clueless about which code to put where

sorry about the trouble

Maydin

May 12, 2011 at 12:05 AM

Hi napernik

Did we give up? :)    i need to make this friendly Urls.  I need your help

Please tell me where to place the code.

thank you

Maydin

Coordinator
May 12, 2011 at 11:22 AM

Hi @Maydin

I'll post the code that replaces media urls in a couple of hours. 

Coordinator
May 12, 2011 at 1:59 PM

Here're the steps.

1. In /App_Code, create MediaUrlReplacer.cs with the following content:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Composite.Data;
using Composite.Data.Types;

namespace Composite.App_Code
{
    public static class MediaUrlReplacer
    {
        private static readonly string MediaUrlPrefix = "/Renderers/ShowMedia.ashx?id=";

        private class Match
        {
            public int Index;
            public string Value;
        }

        public static string MakeMediaUrlsFriendly(string html)
        {
            StringBuilder result = null;

            var internalUrls = new List<Match>();

            int startIndex = 0;

            // We assume that media url has format "/Renderers/ShowMedia.ashx?id={Guid}"

            while (true)
            {
                int urlOffset = html.IndexOf(MediaUrlPrefix, startIndex, StringComparison.OrdinalIgnoreCase);
                if (urlOffset < 0) break;

                int prefixEndOffset = urlOffset + MediaUrlPrefix.Length;

                int endOffset = prefixEndOffset + 36; /* Guid is 36 characters long*/
                if (endOffset >= html.Length) break;

                internalUrls.Add(new Match
                {
                    Index = urlOffset,
                    Value = html.Substring(urlOffset, endOffset - urlOffset)
                });

                startIndex = endOffset;
            }


            internalUrls.Reverse();

            var resolvedUrls = new Dictionary<string, string>();
            using(var conn = new DataConnection())
            foreach (Match mediaUrlMatch in internalUrls)
            {
                string internalMediaUrl = mediaUrlMatch.Value;
                string publicMediaUrl;

                if (!resolvedUrls.TryGetValue(internalMediaUrl, out publicMediaUrl))
                {
                    string guidStr = internalMediaUrl.Substring(MediaUrlPrefix.Length, 36);

                    publicMediaUrl = null;

                    Guid mediaFileId;

                    if(Guid.TryParse(guidStr, out mediaFileId))
                    {
                        IMediaFile mediaFile = conn.Get<IMediaFile>().FirstOrDefault(file => file.Id == mediaFileId);
                        if(mediaFile != null)
                        {
                            publicMediaUrl = GetFriendlyMediaUrl(mediaFile);
                        }
                    }

                    resolvedUrls.Add(internalMediaUrl, publicMediaUrl);
                }
                
                if (publicMediaUrl == null) continue;

                if (result == null)
                {
                    result = new StringBuilder(html);
                }

                result.Remove(mediaUrlMatch.Index, mediaUrlMatch.Value.Length);
                result.Insert(mediaUrlMatch.Index, publicMediaUrl);
            }

            return result != null ? result.ToString() : html;
        }

        private static string GetFriendlyMediaUrl(IMediaFile mediaFile)
        {
            return "/Renderers/ShowMedia.ashx?id=" + mediaFile.CompositePath;
            // return "/media" + mediaFile.FolderPath + "/" + mediaFile.FileName;
        }
    }
}

2. Edit /Renderers/Page.aspx.cs

Find lines:

        using (Profiler.Measure("Changing 'internal' page urls to 'public'"))
        {
            xhtml = PageUrlHelper.ChangeRenderingPageUrlsToPublic(markupBuilder.ToString());            
        }

And change them to:

        using (Profiler.Measure("Changing 'internal' page urls to 'public'"))
        {
            xhtml = PageUrlHelper.ChangeRenderingPageUrlsToPublic(markupBuilder.ToString());
            xhtml = Composite.App_Code.MediaUrlReplacer.MakeMediaUrlsFriendly(xhtml);
        }

After those 2 steps, all media urls on site should look like

/Renderers/ShowMedia.ashx?id=MediaArchive://Manual/Manual_C1_Console.png

3. Once you have it done, edit GetFriendlyUrl() method in MediaUrlReplacer.cs, so it returns
   return "/media" + mediaFile.FolderPath + "/" + mediaFile.FileName;
Not all the media urls should look like
   /media/Manual/Manual_C1_Console.png

4. Use UrlRouting make urls in the previous point work: add redirect from "/media/" to "/Renderers/ShowMedia.ashx?id=MediaArchive://"

I hope you'll succeed, note that after installing an upgrade package you may have to do the step 2 once again

 

 

 

 

 

May 12, 2011 at 3:04 PM
Edited May 12, 2011 at 6:06 PM

maybe the preferred way to replace html in a rendered document would be to attach a Filter to HttpContext.Response.Filter. Since you can easily replace the default rendering-engine with either one that supports Masterpages or one that is 100% MVC a change to /Renderers/Page.aspx.cs could be lost.

May 13, 2011 at 4:18 PM

Hi @napernik

I followed the steps, until step 4 every thing is woking perfectly.  it has been a big help for me, THANK YOU VERY MUCH

now, I am trying UrlRouting to work on IIS 7.5  so far no luck with UrlRewriter.Net,  I'll google it more

Regards

 

Coordinator
May 18, 2011 at 4:54 PM

Hi @maydin later is better than never

I rewritten the example so now you can just add 2 lines to web.config, and copy file http://notepad.cc/share/TDkNGfhRfR to App_Code to get it working.

 

In web.config, edit 

1) Add attribute runAllManagedModulesForAllRequests="true" - so handlers will always be executed

<configuration>

    <system.webServer>

       <modules runAllManagedModulesForAllRequests="true">

2) Add an http module  <add name="ShorterMediaUrls" type="Composite.Examples.ShortMediaUrls.MediaRequestsHttpModule, App_Code" />

<system.webServer>

    ....

   <modules runAllManagedModulesForAllRequests="true">

        <add name="ShorterMediaUrls" type="Composite.Examples.ShortMediaUrls.MediaRequestsHttpModule, App_Code" />

 

you should also have IIS 7.x integrated pipeline mode enabled

May 18, 2011 at 10:05 PM

i guess i should just put something like this in the Contrib-project so everyone can take advantage of friendly mediaurls as well

May 19, 2011 at 12:22 PM

Just updated the CompositeContrib project where i added such a MediaUrlFilter. To enable it just add the following line to your modules-section in web.config. It has been tested on IIS7 integrated pipeline, and you do NOT need to set runAllManagedModulesForAllRequest to true for this module to work.

<add name="MediaUrlFilter" type="CompositeC1Contrib.Web.MediaUrlFilterModule, CompositeC1Contrib" />

Get the newest version here http://compositec1contrib.codeplex.com/SourceControl/list/changesets

May 19, 2011 at 1:30 PM

Sorry for spamming, i should just have added this right away in the first commit, but here it is. For those who prefer a GUID based friendly url instead of exposing the whole path can set an option for that.

  • http://site.local/SubFolderApp/media/dubbedub/some_folder/some_other_folder/Jellyfish.jpg - this is the default format
  • http://site.local/SubFolderApp/media/6d4ecd56-e965-48c5-a811-bdd08b454986/Jellyfish.jpg - if you prefer this, set useFolderPathsForMediaUrls to false in appSettings
May 20, 2011 at 6:55 PM

Thank you guys

Coordinator
Jun 24, 2011 at 12:46 PM

I checked in some changes. In the next version media urls will be stored as:

~/media(6d4ecd56-e965-48c5-a811-bdd08b454986)

instead of

~/Renderers/ShowMedia.ashx?id=6d4ecd56-e965-48c5-a811-bdd08b454986

and will be renderered as:

[/Subfolder]/media/6d4ecd56-e965-48c5-a811-bdd08b454986/Fish/Jellyfish.jpg

which is similar to CompositeC1Contrib's behaviour with useFolderPathsForMediaUrls="true"

Jun 24, 2011 at 5:54 PM

Hi @napernik,

This is great, thanks alot.

By the way, I have another problem that i am using CompositeC1Contrib NicerUrls feature,  I also installed Composite.News package.  Inna told me these 2 packages are not working together.

She suggested me to d/l the news package and modify it. I dont have any clue about how to modify the News package, I cannot make Composite.News package and CompositeC1Contrib NicerUrls work together.

I already posted my problem but no help so far. I though you may help me. can you take a look at it. 

Thanks in advance napernik

Have a nice weekend