orchardcmsorchardcms-1.10

How to efficiently return multiple DriverResults from the Display method?


This article describes how to write efficient DisplayDrivers for your Parts so that expensive code is only executed when the shape is actually displayed.

protected override DriverResult Display(MyPart part, string displayType, dynamic shapeHelper)
{
    // return the shape
    return ContentShape("Parts_MyPart", () => {
        // do computations here
        return shapeHelper.Parts_MyPart();
    });
}

Now I'd like to make a Part that returns multiple DriverResults using the Combine method, with each DriverResult containing mostly the same data, which is fetched from the database. The problem is I can't think of a good way to make it efficient, since Combine doesn't take a Func parameter.

protected override DriverResult Display(MyPart part, string displayType, dynamic shapeHelper)
{
    var data = ... // expensive query specific to the part

    return Combined(
       ContentShape("Parts_MyPart_A", () => shapeHelper.Parts_MyPart_A(
            Data: data
        )),
        ContentShape("Parts_MyPart_B", () => shapeHelper.Parts_MyPart_B(
            Data: data
        )),
        ContentShape("Pars_MyPart_C", ...
    );
}

Can I achieve the same result so that the query is not executed if nothing is displayed and only executed once if multiple shapes are displayed?

I want to do this so I can display the same data on a ContentItem's Detail in different zones with different markup and styling. An alternative approach could be to return one shape that in turn pushes other shapes into different zones but then I would lose the ability to use Placement to control each of them individually.


Solution

  • I'd probably add a lazy field to your Part.

    public class MyPart : ContentPart {
        internal readonly LazyField<CustomData> CustomDataField = new LazyField<CustomData>();
    
        public CustomData CustomData {
          get { return CustomDataField.Value; }
        }
    }
    
    public class CustomData {
        ...
    }
    
    public class MyPartHandler : ContentPartHandler {
    
       private ICustomService _customService;
    
       public MyPartHandler(ICustomService customService){
          _customService = customService;
          OnActivated<MyPart>(Initialize);
       }
    
       private void Initialize(ActivatedContentContext context, MyPart part){
             part.CustomDataField.Loader(() => {
                 return _customService.Get(part.ContentItem.Id);
             });
       }
    }
    

    It will only be calculated if it is loaded and all the shapes will share the calculated value.