Display nested content types in parts problem

Topics: Customizing Orchard, Troubleshooting, Writing modules
Jun 20, 2011 at 4:43 PM

Hi,

I'm new to Orchard, love it but I've got a small problem.

Background: I created a module in which I created Partner content type and PartnerPart. Partner content type is pretty "normal" type with PartnerPart, CommonPart etc - nothing fancy.

I need to display list of Partners in a widget so I created ListPartner with ListPartnerPart, and in ListPartnerPartDriver I just query IContentManager to get PartnerParts. This works OK.

But here's the problem - in details Partner view (where full Body is displayed) I also need to display "Other Partners" list, so this list must not contain the Partner that is currently visible. So I added ListPartnerPart to Partner type and in ListPartnerPartDriver I have:

 

protected override DriverResult Display( ListPartnerPart part, string displayType, dynamic shapeHelper )
		{
			var parentContentItem = part.ContentItem;
			int ignorePartnerId= -1;
			if( part.ShowOtherThanContainerPartner && parentContentItem.ContentType == "Partner" ) 
			{
				ignorePartnerId= parentContentItem.Id;
			}
// get list ignoring current partner
			var partnersList = _partnerService.GetList( ignorePartnerId, currentCultureRecord, 0, part.Count, VersionOptions.Published );


			var list = shapeHelper.List();
// here's the error:
			list.AddRange( partnersList.Select( bp => _contentManager.BuildDisplay( bp, "Summary" ) ) );

			return ContentShape( shapeHelper.Parts_Partner_List( ContentPart: part, ContentItems: list ) );
		}

partnersList seems ok, but when building Summary (_contentManager.BuildDisplay) list with them, I get StackOverflow exception. I guess it's because PartnerPart is in Partner, and Partner has ListPartnerPart also, which gets rendered with Display method above and so on...

Is there any way to fix it using parts or should I direct myself to using Controllers instead?

thanks in advance!

Jun 20, 2011 at 10:14 PM
Edited Jun 20, 2011 at 10:15 PM

There are two steps to fixing this;

1. Refactor so that BuildDisplay happens in a factory method. This is the normal way to do things from drivers; it means that if placement stops the part being displayed, then the factory code won't get run at all. So your recursive BuildDisplay call will only get run for parts that are being displayed.

In factory form your Display method would look like this: (sorry about the formatting!)

 

protected override DriverResult Display( ListPartnerPart part, string displayType, dynamic shapeHelper )
		{
			return ContentShape( "Parts_Partner_List", ()=>{

			var parentContentItem = part.ContentItem;
			int ignorePartnerId= -1;
			if( part.ShowOtherThanContainerPartner && parentContentItem.ContentType == "Partner" ) 
			{
				ignorePartnerId= parentContentItem.Id;
			}
// get list ignoring current partner
			var partnersList = _partnerService.GetList( ignorePartnerId, currentCultureRecord, 0, part.Count, VersionOptions.Published );


			var list = shapeHelper.List();
// here's the error:
			list.AddRange( partnersList.Select( bp => _contentManager.BuildDisplay( bp, "Summary" ) ) );

return shapeHelper.Parts_Partner_List( ContentPart: part, ContentItems: list );
                      };
		}

 

2. In Placement, hide the Parts_Partner_List shape on "Summary" display view. Even better, only show it on detail view. That should look like this:

 

<Match DisplayType="Detail">
    <Place Parts_Partner_List="Content:5"/>
</Match>

So then, placement stops the sub-displays getting rendered, and hence the recursion-causing code won't run.

Jun 20, 2011 at 10:46 PM

Thank you very much randompete.

I wasn't aware that

return ContentShape( "Parts_Partner_List",...

 

is different than:

 

return ContentShape( shapeHelper.Parts_Partner_List(...

 

I'll try it out, I also wanted to try method described here:

http://www.deepcode.co.uk/2011/06/real-world-orchard-cmspart-7finding.html

that is creating a widget and get content id with provided TryGetCurrentContentId. But I like this approach better.

Thanks!

Jun 21, 2011 at 3:15 AM

The different part is the function delegate that looks like: ()=>{ return shapeHelper.Foo(...); }

It's defining a function that will get executed only if the shape needs displaying in the end. For your case in particular you will suffer a performance hit if you don't use it!

Jun 21, 2011 at 5:00 PM
randompete wrote:

The different part is the function delegate that looks like: ()=>{ return shapeHelper.Foo(...); }

It's defining a function that will get executed only if the shape needs displaying in the end. For your case in particular you will suffer a performance hit if you don't use it!

Great, your advice worked like a charm :)

Jul 28, 2012 at 12:58 PM
Edited Jul 28, 2012 at 1:02 PM

if your looking for better Approach you can use this new TryGetCurrentContentId(...) for remove dependency to RoutePart or AutoroutePart:

        private int TryGetCurrentContentId()
        {
            return Convert.ToInt32(_WorkContextAccessor.GetContext().HttpContext.Request.RequestContext.RouteData.Values["Id"]);
        }