Mark Hall
Nov 7, 2016
  5388
(7 votes)

Anonymous Project Preview

A little while ago a colleague of mine wrote an excellent post on how to create a partial route to preview specific versions of content here.  Some comments wanted to see this in the context of the projects as well as be able to allow anonymous users to be able to preview the page.  I created a small sample that addresses the issues in a different way then the partial route.

First we will create an interface for dealing with getting project identifiers and content.  In the sample we are using the host header, but you could use cookies, session, or anything else you would like to enable.  Using the url seemed most easy and less places to change the code.

using EPiServer.Core;
using System.Web;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    /// <summary>
    /// Helps identify project and version in the anonymous context
    /// </summary>
    public interface IPreviewProjectIdentifier
    {
        /// <summary>
        /// Gets the project version of the content, otherwsie returns the published reference.
        /// </summary>
        /// <param name="publishedContentReference"></param>
        /// <param name="httpContextBase"></param>
        /// <returns>A <see cref="ContentReference"/></returns>
        ContentReference GetProjectVersion(ContentReference publishedContentReference, HttpContextBase httpContextBase);

        /// <summary>
        /// Gets the current project id based on custom logic.  If there is no current project returns 0/
        /// </summary>
        /// <param name="httpContextBase"></param>
        /// <returns>The primary key <see cref="int"/> of the project</returns>
        int GetProjectId(HttpContextBase httpContextBase);

    }
}

Now we will create the implementation.

using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.ServiceLocation;
using EPiServer.Web.Hosting;
using System.Linq;
using System.Web;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    [ServiceConfiguration(Lifecycle = ServiceInstanceScope.Singleton, ServiceType = typeof(IPreviewProjectIdentifier))]
    public class DefaultPreviewProjectIdentifier : IPreviewProjectIdentifier
    {
        private readonly ProjectRepository _projectRepository;

        public DefaultPreviewProjectIdentifier(ProjectRepository projectRepository)
        {
            _projectRepository = projectRepository;
        }

        public ContentReference GetProjectVersion(ContentReference publishedReference, HttpContextBase httpContextBase)
        {
            var projectId = GetProjectId(httpContextBase);
            return projectId == 0 ? publishedReference : GetProjectReference(publishedReference, projectId);
        }

        public int GetProjectId(HttpContextBase httpContextBase)
        {
            var uri = GetUrl(httpContextBase);
            if (!uri.Host.StartsWith("project"))
            {
                return 0;
            }

            var projectId = uri.Host.Substring(uri.Host.IndexOf("-") + 1, uri.Host.IndexOf(".") - (uri.Host.IndexOf("-") + 1));
            int id;
            return int.TryParse(projectId, out id) ? id : 0;
        }

        private ContentReference GetProjectReference(ContentReference publishedReference, int projectId)
        {
            var items = _projectRepository.ListItems(projectId);
            if (items == null)
            {
                return publishedReference;
            }

            var item = items.FirstOrDefault(x => x.ContentLink.ToReferenceWithoutVersion() == publishedReference.ToReferenceWithoutVersion());
            return item == null ? publishedReference : items.FirstOrDefault(x => x.ContentLink.ID == item.ContentLink.ID).ContentLink;
        }

        private static UrlBuilder GetUrl(HttpContextBase httpContext)
        {
            UrlBuilder hostUrl;
            if (httpContext != null && httpContext.Request.Url != null)
            {
                hostUrl = new UrlBuilder(httpContext.Request.Url.AbsoluteUri)
                {
                    Path = VirtualPathUtility.AppendTrailingSlash(GenericHostingEnvironment.Instance.ApplicationVirtualPath)
                };
            }
            else
            {
                hostUrl = new UrlBuilder(VirtualPathUtility.AppendTrailingSlash(GenericHostingEnvironment.Instance.ApplicationVirtualPath));
            }
            return hostUrl;
        }
    }
}

Next we need to create a custom IFilterProvider for MVC.  We need to do this because the base class ContentController<T> has the AuthorizeContentAttribute.  This is responsible checking the access rights of the content and will prevent an anonymous user from seeing the content.

using EPiServer.ServiceLocation;
using EPiServer.Web.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace EPiServer.Reference.Commerce.Site.Infrastructure
{
    public class ProjectFilterProvider : IFilterProvider
    {
        private readonly FilterProviderCollection _filterProviders;
        private readonly Type _authorizeContent = typeof(AuthorizeContentAttribute);
        private readonly IPreviewProjectIdentifier _previewProjectIdentifier = ServiceLocator.Current.GetInstance<IPreviewProjectIdentifier>();

        public ProjectFilterProvider(IList<IFilterProvider> filters)
        {
            _filterProviders = new FilterProviderCollection(filters);
        }

        public IEnumerable<Filter> GetFilters(ControllerContext controllerContext,
            ActionDescriptor actionDescriptor)
        {
            var filters = _filterProviders.GetFilters(controllerContext, actionDescriptor);
            var projectId = _previewProjectIdentifier.GetProjectId(controllerContext.HttpContext);
            return projectId > 0 ? filters.Where(x => x.Instance.GetType() != _authorizeContent) : filters;
        }
    }
}

Finally we will register the filter and add a event handler to IContentRouteEvents.RoutedContent to update the content to the correct version in the context of the project.

[ModuleDependency(typeof(ServiceApi.IntegrationInitialization), typeof(EPiServer.Commerce.Initialization.InitializationModule))]
    public class SiteInitialization : IConfigurableModule
    {
        public void Initialize(InitializationEngine context)
        { 
            var providers = FilterProviders.Providers.ToList();
            FilterProviders.Providers.Clear();
            FilterProviders.Providers.Add(new ProjectFilterProvider(providers));
            
            context.Locate.Advanced.GetInstance<IContentRouteEvents>().RoutedContent += ContentRoute_RoutedContent;
            
        }

        private void ContentRoute_RoutedContent(object sender, RoutingEventArgs e)
        {
            e.RoutingSegmentContext.RoutedContentLink = ServiceLocator.Current.GetInstance<IPreviewProjectIdentifier>().GetProjectVersion(e.RoutingSegmentContext.RoutedContentLink, HttpContext.Current.ContextBaseOrNull());
        }
    }
}

Now we have a project preview for anonymous users where they can preview a live site in the context of a specific project.

http://www.screencast.com/t/FMbCw188X

Nov 07, 2016

Comments

Nov 10, 2016 11:14 AM

This is awesome, cheers Mark. Been on my personal back log for a long time so you have saved me a job :)!

Please login to comment.
Latest blogs
Missing Properties tool for Optimizely CMS

If you have been working with Optimizely CMS for a while you have probably accumulated some technical debt in your property definitions. When you...

Per Nergård (MVP) | Mar 10, 2026

AI Generated Optimizely Developer Newsletter

Updates in the Optimizely ecosystem are everywhere: blog posts, forums, release notes, NuGet packages, and documentation changes. This newsletter...

Allan Thraen | Mar 10, 2026 |

Lessons from Building Production-Ready Opal Tools

AI tools are becoming a normal part of modern digital platforms. With  Optimizely Opal , teams can build tools that automate real tasks across the...

Praful Jangid | Mar 7, 2026

My Takeaway from Optimizely Opal Agents in Action 2026 - What Agentic AI Means for the Future of Digital Marketing

I would like to share with you what stayed in my head after this amazing virtual event organized by Optimizely. Agents in Action 2026 , a live...

Augusto Davalos | Mar 6, 2026

From Vision to Velocity: Introducing the Optimizely MVP Technical Roundtable

Digital transformation is a two-sided coin. On one side, you have the high-level strategy, the business cases, the customer journeys, and the...

Patrick Lam | Mar 6, 2026

Commerce 14.45.0 is incompatible with CMS 12.34.2 (but that's an easy fix!)

Incompatible is a strong word, but that is to get your attention. This is one of the small thing that can be overlooked, but if you run into it, it...

Quan Mai | Mar 5, 2026