November Happy Hour will be moved to Thursday December 5th.

Displaying the same content at multiple places in the page tree (with MVC routing?)

Vote:
 

Hello!

We have a website where we want to display the same content at multiple places in the page tree.

To make the life easier for editors and avoid having to create the same content several times we would like to create one page and then display it at multiple places in the page tree. This could be solved by creating pages at these locations and use EPis shortcut function and "Fetch content from page..." but the editors whould still have to create many pages.

For example let's say we have three pages: "SectionA", "SectionB" and "SectionC". They should all have a child page called "PageA".

/SectionA/PageA
/SectionB/PageA
/SectionC/PageA

Whenever any of these URL:s would be requested we would like to check if the page exists in the EPi page tree. If it does it should display the page, and if not is should look for the page at another location (for example "/Global/PageA") and display that page's content as if it existed at the requested URL.

Is there a simple solution where this could be done with MVC routing? Or maybe some other solution?

#89597
Aug 20, 2014 15:07
Vote:
 

My initial reaction to this question was to say to use a shared block, but you'd still need to create the pages and drag the block into a content area. Personally, I think this is the best way to go.

But you could possibly accomplish what you want by using partial routing and some site settings. So with that option, a couple questions:

- Will "PageA" ever have child pages managed in EPiServer?

- Are the "SectionA", "SectionB", and "SectionC" pages the same page type?

#89598
Aug 20, 2014 15:23
Vote:
 

Thanks for the reply Chris. A shared block could be used but the issue is that there are hundreds of pages with the same content. The editors feel that there will be to much work creating pages.

Yes, "PageA" will have child pages.

"SectionA", "SectionB", and "SectionC" may be different page types.

#89617
Aug 21, 2014 8:40
Vote:
 

Will the children of "PageA" have different content in each section?

#89636
Aug 21, 2014 11:00
Vote:
 

Nope, "PageA" will have the exact same content everywhere. Thats why we want to store it in a global place and replicate the content at several locations. So when I edit "/Global/PageA" the changes are also displayed in the exact same way for "/SectionA/PageA", "/SectionB/PageA" and "/SectionC/PageA".

#89638
Aug 21, 2014 11:16
Vote:
 

My question was about the children of "PageA"...

Will there be "/SectionA/PageA/SubPageA" and "/SectionB/PageA/SubPageB"?

Or will all of the children of "PageA" only be under "/Global/PageA/"?

#89639
Aug 21, 2014 11:19
Vote:
 

OK I missed that part :)

"PageA"'s children will be exactly the same under each section.

The entire structure of child pages would be created globally. All of the children of "PageA" will be under "/Global/PageA/.

#89640
Aug 21, 2014 11:29
Vote:
 

Ok. So it looks like you might be able to handle this with a partial router, but this might cause issues with other things (which I haven't explored). This might at least get you started on the path you are trying to accomplish.

Here's the partial router:

public class GlobalContentPartialRouter : IPartialRouter
    {
        public object RoutePartial(PageData content, SegmentContext segmentContext)
        {
            var loader = ServiceLocator.Current.GetInstance();
            
            // The reference to the global folder (sitting under the Root) is found in my StartPage properties
            var rootContentReference = loader.Get(ContentReference.StartPage).GlobalContentReference;

            // Put each of the URL segments from the RemainingPath into a List
            var urlSegments = segmentContext.RemainingPath.Split('/').Where(s => !string.IsNullOrEmpty(s)).ToList();

            PageData routedContent = null;

            // We'll go through each URL segment one at a time to ensure the proper page hierarchy stays intact
            foreach (var segment in urlSegments)
            {
                // Make sure we have a ContentReference to get children
                if (ContentReference.IsNullOrEmpty(rootContentReference))
                {
                    return null;
                }

                // Get the child where the URL segment matches
                routedContent = loader.GetChildren(rootContentReference).FirstOrDefault(c => c.URLSegment == segment);

                if (routedContent != null)
                {
                    rootContentReference = routedContent.ContentLink;
                }
                else
                {
                    // If no child is found, the page doesn't exist in the global folder, or however deep in the page tree we are
                    // So we immediately return null (404)
                    return null;
                }
            }

            // Retun null (404) if nothing was found
            if (routedContent == null)
            {
                return null;
            }

            // Setting the RemainingPath to string.Empty was important in order to get this to work
            segmentContext.RemainingPath = string.Empty;
            segmentContext.RoutedContentLink = routedContent.ContentLink;

            // Return the routedContent that was found.
            return routedContent;
        }

        public PartialRouteData GetPartialVirtualPath(PageData content, string language, RouteValueDictionary routeValues, RequestContext requestContext)
        {
            // This might be able to be implemented some how...
            return null;
        }
    }

And to use it, here's the initialization module:

[InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class GlobalContentPartialRouterInitialization : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            var partialRouter = new GlobalContentPartialRouter();
            RouteTable.Routes.RegisterPartialRouter(partialRouter);
        }

        public void Preload(string[] parameters) { }

        public void Uninitialize(InitializationEngine context) { }
    }

Then you can set up your page tree like this:

-Root
--Global
---PageA
----SubPageA
---PageB
----SubPageB
--StartPage
---SectionA
---SectionB
---NotASection

These are valid URLs:

- domain.com/en/SectionA
- domain.com/en/SectionA/PageA
- domain.com/en/SectionA/PageA/SubPageA
- domain.com/en/NotASection/PageA/SubPageA
- domain.com/en/PageA/SubPageA

You get the idea...

#89650
Edited, Aug 21, 2014 14:46
Vote:
 

Some issues with this approach:

  • You'll be able to get the global pages mostly by added "/PageA" to the end of the URL. You could fix this by making the "SectionA" page type inherit from a base class (or be of all one page type), then in the partial router you would specify to use that base class instead of PageData (the TContent type).
  • You might not be able to easily create a page link to a global page (the TRoutedData type and GetPartialVirtualPath() method). You'll probably have to hard-code how the link is created, which is not very flexible.
  • You're might lose page heirarchy functionality when inside a global page or subpage, which will make menus, navigations, breadcrumbs, etc. a pain to deal with.

So obviously there are pro's and con's to both approaches (blocks vs. partial router)...

#89653
Edited, Aug 21, 2014 14:56
Vote:
 

I have not really tested it myself but there is a ClonedContentProvider in the Alloy sample packages that appears to do what you are looking for.

#89657
Aug 21, 2014 15:54
Vote:
 

That looks very promising! Thank you both for your input.

#89704
Aug 22, 2014 13:04
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.