Routing issue with custom ContentProvider

Sebbe
Member since: 2012
 

Hi!

The support asked me to post a question here to check if anyone else experienced the same problem. I have been working on converting PageProviders to ContentProviders in an upgrade of a site from 6 R2 to 7.19. 

The aim of the provider is to mirror content (pages) from one site to another (Holmen to Iggesund). The sites share the same code base and EPiServer-instance. The mirrored pages should be of a different pagetype than the original pages. I have looked at the implementation of the Youtube provider so the code should be quite similar to that: https://github.com/episerver/YouTubeContentProvider. I have gotten so far so that everything works like a charm in edit mode, but for some reason the routing is not working on the site. When browsing to the respective pages I first got a 404. I added an override of the method ListMatchingSegments(). As far as I can see that method now returns the correct content, but instead of the page being displayed I get a 301 redirect loop: 

When I browse to http://localhost/en/Global/Press-Startpage/Mirrored-News-from-Holmen/holmen-and-sustainability

I get the response:

Object moved

Object moved to here.


"holmen-and-sustainability" is the name of the page in this case (which I would like to use as routing segment). "?id=21__newsip&epslanguage=en" is added to the redirect url and I suspect that the ContentProvider once again tries to resolve the content after the redirect and the loop is a fact.

I also tried regestering a partial router, but that code doesn't trigger when I have the ListMatchingSegments() method in the ContentProvider.

Grateful for any help!

/Sebbe

Here is the ContentProvider:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Web.Caching;
using System.Xml.Linq;
using EPiServer;
using EPiServer.Core;
using Holmen.Externweb.Common;
using EPiServer.DataAbstraction;
using Holmen.Externweb.Web.Templates.Public.PageTypes;
using Holmen.Externweb.Web.Templates.Public.Classes.ScheduledJobs;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Construction;
using EPiServer.DataAccess;
using EPiServer.Web.Routing;
using EPiServer.Web;
using System.Globalization;


namespace Holmen.Externweb.Web.Templates.Public.Classes.ContentProviders
{

/**
* This replicates Holmen Group news marked for display on Iggesund
*/
//TODO Epi7 man kan ärva från IVersion för att hantera publiseringsinfo. 
public class NewsForIggesundProvider : EPiServer.Core.ContentProvider
{

private const string languageBranch = "en";
private const string backingPagePropertyName = "HolmenNewsPage";

private readonly IdentityMappingService _identityMappingService;

private ContentType ContentType
{
get { return ContentTypeRepository.Load(typeof(EpiPageTypes.Iggesund.Article.IggesundArticleColumnsFromHolmenNews)); }
}

public NewsForIggesundProvider(IdentityMappingService identityMappingService)
{
_identityMappingService = identityMappingService;
}

/// 
/// Initializes the provider from configuration settings.
/// 
/// The key.
/// The config params.
public override void Initialize(string key, NameValueCollection configParams)
{
//Let base classes do their initialization
base.Initialize(key, configParams);
}

protected override IContent LoadContent(ContentReference contentLink, ILanguageSelector languageSelector)
{
MappedIdentity mappedIdentity = _identityMappingService.Get(contentLink);

string holmenNewsId = mappedIdentity.ExternalIdentifier.Segments[1];
int holmenNewsIdInt;
var success = int.TryParse(holmenNewsId, out holmenNewsIdInt);
if (success)
{
var reference = new PageReference(holmenNewsIdInt);
PageData page = DataFactory.Instance.GetPage(reference);

var iggesundArticle = ConvertToIggesundArticle(mappedIdentity, page);

return iggesundArticle;
}
return null;
}



protected override IList LoadChildrenReferencesAndTypes(ContentReference contentLink, string languageID, out bool languageSpecific)
{
languageSpecific = false;

if (contentLink == this.EntryPoint)
{
PageReference pr = Holmen.Externweb.Web.EpiPageTypes.HolmenGroup.HolmenGroupSettings.Instance.NewsRootPage;

if (!PageReference.IsNullOrEmpty(pr))
{
System.Collections.Generic.IList list = new List();

var pages = pr.GetChildren().OfType().Where(x => x.ShowOnIggesund); //.Where(x => x.Categories.IsEmpty);
foreach (EpiPageTypes.HolmenGroup.Press.NewsItem page in pages)
{
Uri externalID = MappedIdentity.ConstructExternalIdentifier(ProviderKey, page.ContentLink.ToString());
var mappedIdentity = _identityMappingService.Get(externalID, true);

System.Type modelType = null;

modelType = typeof(EpiPageTypes.Iggesund.Article.IggesundArticleColumnsFromHolmenNews);

GetChildrenReferenceResult result = new GetChildrenReferenceResult
{
ContentLink = mappedIdentity.ContentLink,
ModelType = modelType
};
list.Add(result); 
}
return list.Reverse().ToList();
}
}
return null;

}


protected override IList ListMatchingSegments(ContentReference parentLink, string urlSegment)
{
var list = new List();
foreach (var child in LoadChildren(parentLink, LanguageSelectorFactory.MasterLanguage(), -1, -1))
{
var routable = child as IRoutable;
var isMatch = routable != null && urlSegment.Equals(routable.RouteSegment, StringComparison.OrdinalIgnoreCase);

if (isMatch)
{
list.Add(new MatchingSegmentResult
{
ContentLink = child.ContentLink
});
}
}
return list;
}



public IContent ConvertToIggesundArticle(MappedIdentity mappedIdentity, PageData original)
{
ContentType type = ContentTypeRepository.Load(typeof(EpiPageTypes.Iggesund.Article.IggesundArticleColumnsFromHolmenNews));


var mirrored = ContentFactory.CreateContent(type);

mirrored.ContentTypeID = type.ID;

mirrored.ParentLink = new PageReference(EntryPoint.ID);

mirrored.ContentLink = mappedIdentity.ContentLink;
mirrored.ContentGuid = mappedIdentity.ContentGuid;

mirrored.Property[backingPagePropertyName].Value = original.ContentLink;

mirrored.Name = original.Name;

ILocalizable localizable = mirrored as ILocalizable;
var cult = new CultureInfo(languageBranch);
localizable.Language = cult;

(mirrored as EpiPageTypes.Iggesund.Article.IggesundArticleColumnsFromHolmenNews).LinkType = PageShortcutType.Normal;
(mirrored as EpiPageTypes.Iggesund.Article.IggesundArticleColumnsFromHolmenNews).Property["PageShortcutLink"].Value = original.ContentLink;

(mirrored as IRoutable).RouteSegment = UrlSegment.GetUrlFriendlySegment(mirrored.Name);

var securable = mirrored as IContentSecurable;
securable.GetContentSecurityDescriptor().AddEntry(new AccessControlEntry(EveryoneRole.RoleName, AccessLevel.Read));

var versionable = mirrored as IVersionable;
if (versionable != null)
{
versionable.Status = VersionStatus.Published;
}

var changeTrackable = mirrored as IChangeTrackable;
if (changeTrackable != null)
{
changeTrackable.Changed = original.Changed;
}

return mirrored;
}
}
}



#117993 Feb 25, 2015 14:53
  • Erik Täck
    Member since: 2006
     

    You do need a partial router, otherwise it will never work in "view mode".

    I added an override for the "GetUniqueUrlSegment" mehtod and rolled my own url (wich was essentially "[externalid]-friendlyname") to get unique url segments to work with.

    Then I called "GetBySegment" on my provider during the RoutePartial, which will match whatever content item have that particular urlsegment. I am also not sure what exaxtly I am supposed to do in the GetPartialVirtualPath method, I just used return null and it worked fine for me.

    [ModuleDependency(typeof(Initializer))]
    	public class JobOpeningRouter : IPartialRouter<IContent, JobOpening>, IInitializableModule
    	{
    		public void Initialize(InitializationEngine context)
    		{
    			RouteTable.Routes.RegisterPartialRouter(this);
    		}
    
    		public void Uninitialize(InitializationEngine context)
    		{
    		}
    
    		public void Preload(string[] parameters)
    		{
    		}
    
    		protected Injected<JobOpeningContentProvider> JobContentProvider { get; set; }
    
    		public object RoutePartial(IContent content, SegmentContext segmentContext)
    		{
    			try {
    				if(content.ContentLink.CompareToIgnoreWorkID(JobContentProvider.Service.EntryPoint)) {
    					var segment = segmentContext.GetNextValue(segmentContext.RemainingPath);
    					return JobContentProvider.Service.GetBySegment(content.ContentLink, segment.Next, null);
    				}
    			}
    			catch(NullReferenceException) {
    				// JobContentProvider.Service.EntryPoint sometimes throw a nullreferenceexception in certain contexts (like block edit mode..)
    			}
    
    			return content;
    		}
    
    		public PartialRouteData GetPartialVirtualPath(JobOpening content, string language, RouteValueDictionary routeValues, RequestContext requestContext)
    		{
    			// this is already handled by epi
    			return null;
    		}
    	}



    #118206 Mar 02, 2015 14:33
  • Sebbe
    Member since: 2012
     

    Thanks! I will give it a try again and see if I can get it working. 

    #118211 Mar 02, 2015 15:20
  • GOSSO
    Member since: 2007
     

    Hey Sebbe!

    It seems that you need to activate ClassicLinkRoute i global asax, No need for IPartialRouter

             protected override void RegisterRoutes(RouteCollection routes)
            {
                base.RegisterRoutes(routes);
                routes.Insert(0, ServiceLocator.Current.GetInstance<ClassicLinkRoute>());
            }
    #118455 Mar 06, 2015 14:42
  • Sebbe
    Member since: 2012
     

    Great Luc! Can't believe I haven't stumbled across that while searching for solutions to the problem!

    #118471 Mar 06, 2015 16:25
  • Dan Matthews
    Member since: 2006
     

    Hi Sebbe, THANK YOU! Your pointing out of the need to override ListMatchingSegments just stopped me banging my head against the wall:)

    Here is my method for that, similar to yours buut avoids the need to override LoadChildren:

            protected override IList<MatchingSegmentResult> ListMatchingSegments(ContentReference parentLink, string urlSegment)
            {
                var list = new List<MatchingSegmentResult>();
    
                bool languageSpecific;
    
                var children = LoadChildrenReferencesAndTypes(parentLink, null, out languageSpecific);
    
                foreach(var child in children)
                {
                    var content = LoadContent(child.ContentLink, null);
    
                    if(content is IRoutable && (content as IRoutable).RouteSegment.Equals(urlSegment, StringComparison.InvariantCultureIgnoreCase))
                    {
                        list.Add(new MatchingSegmentResult() { ContentLink = content.ContentLink });
                    }
                }
               
                return list;
            }
    #162793 Oct 16, 2016 7:48
  • GOSSO
    Member since: 2007
     

    Great Dan, ContentProviders can be tricky to get setup right, so i've put together a series of blog posts about it. http://devblog.gosso.se/tag/contentprovider/

    #162834 Oct 17, 2016 22:31