London Dev Meetup Rescheduled! Due to unavoidable reasons, the event has been moved to 21st May. Speakers remain the same—any changes will be communicated. Seats are limited—register here to secure your spot!

Routing issue with custom ContentProvider

Vote:
 

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
Vote:
 

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
Vote:
 

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

#118211
Mar 02, 2015 15:20
Vote:
 

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
Vote:
 

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

#118471
Mar 06, 2015 16:25
Vote:
 

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
Vote:
 

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
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* 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.