LazyField<T>

What is it

LazyField<T> is a utility class that lives in the Orchard.ContentManagement.Utilities namespace and enables you to return a value in a lazy manner.
It's public members are:

class LazyField<T> {
    T Value { get; set; }
    void Loader(Func<T, T> loader);
    void Setter(Func<T, T> setter);
}

If LazyField is used without configuring a loader and / or setter delegate, it will simply act as a backing field.

When to use it

Sometimes, when developing content parts, you may come across the need to load related data, for example one or more content items.
Or perhaps you want to expose a service from your content part, or implement a computed property that requires some service class.

Because you cannot inject dependencies into content parts using the constructor, you will have to do so manually.
For example, you could write a content handler and handle the Activated event to manually set some properties on the activated content item.
Although that would work, this means that on each request and when each content item is activated, the related data is loaded. This may impact the performance negatively.
So instead, you will want to load the related data in a lazy manner.

How to use it 

In order to implement a lazy field, you will have to do two things:

  1. Create a public or internal member on your class (for example your own content part) of type LazyField<T>, where T is the type of the object to be loaded in a lazy manner. 
  2. Configure the lazy field member by providing a getter and optionally a setter delegate.

For example, imagine we have a part called CustomerPart<CustomerPartRecord>. CustomerPartRecord has a property called InvoiceAddressId of type int, which is stores the id of an Address content item.
The part and record classes look like this:

public class CustomerPart : ContentPart<CustomerPartRecord> {
    
}

public class CustomerPartRecord : ContentPartRecord {
    public virtual int InvoiceAddressId { get; set; }
}

public class AddressPart : ContentPart { }

Note that I didn't create the InvoiceAddressId property on the CustomerPart. Although I could have done so, what I want to do instead is create a property called InvoiceAddress of type Address.
This enables use to access the related Address content item without having to use the ContentManager ourselves everytime we need to load it. Also, the LazyField<T> instance will hold a reference to the loaded object, so we don't have to worry about loading the Address multipel times during the same request.

Let's start by implementing the lazy field as the backing store and a property that leverages the field: 

public class CustomerPart : ContentPart<CustomerPartRecord> {
   
   // Define the lazy field that will act as the backing store for the Address content item.
   // Note that we defined it as a internal because we need to access this field from the outside
   // in order to configure the setter and getter.
   internal readonly LazyField<AddressPart> InvoiceAddressField = new LazyField<AddressPart>();

   // Define a property that provides access to the lazy field value
   public AddressPart InvoiceAdddress {
      get { return InvoiceAddressField.Value; }
      set { InvoiceAddress.Value = value; }
}

Now that we have the lazy field in place, the next thing we need to do is configure it as soon as our CustomerPart is loaded / activated.
We'll do this from a content handler:

public class CustomerPartHandler : ContentPartHandler {

   private IContentManager _contentManager;

   public CustomerHandler( IContentManager contentManager ){
      _contentManager = contentManager;
      OnActivated<CustomerPart>(SetupCustomerPart);
   }
   
   private void SetupCustomerPart(ActivatedContentContext context, CustomerPart part){
      
         // Setup the getter of the lazy field
         part.InvoiceAddressField.Loader( addressPart => _contentManager.Get<AddressPart>(part.Record.InvoiceAddressId));

        // Setup the setter of the lazy field
        part.InvoiceAddressField.Setter( addressPart => {
        part.Record.InvoiceAddressId = addressPart != null ? addressPart.Id : 0;
        return addressPart;
     });
   }
}

 

Essentially, we are passing in a delegate to the loader that in turn uses the injected ContentManager to load the Address item when the LazyField is first accessed.
The setter delegate simply gets the ID of the specified Address item and stores it in the record's InvoiceAddressId property.

Alternative approach

In the previous example, we showed how to use LazyField<T> to load related content using the ContentManager. The primary reasons to use the LazyField were:

  1. To load the related content using a service class (ContentManager) in a lazy manner
  2. To load the related content only once, on first access.

However, because all content parts have access to the ContentItem of which they're part of, we actually do have access to the ContentManager from within our part.
So alternatively, we could have implemented the InvoiceAddress property as follows:

public class CustomerPart : ContentPart<CustomerPartRecord> {
   
   private AddressPart _invoiceAddress;

   public AddressPart InvoiceAdddress {
      get { return _invoiceAddress ?? (_invoiceAddress = ContentItem.ContentManager.Get<AddressPart>(Record.InvoiceAddressId)); }
      set { 
         _invoiceAddress = value;
         Record.InvoiceAddressId = value != null ? value.Id : 0;
      }
}

This code will work as well and doesn't require configuring a LazyField from the content handler.
However, I think this is less elegant than the LazyField approach, since we now have data access logic inside of the content part.

Source code

Download source code: https://lazyfielddemo.codeplex.com/releases/view/95579

Related Articles

Tutorial: Content Part Editors

Tags: LazyField

3 Comments

  • SciencApp said

    <p>Can you please send us a practical implementation of this idea. Something simple and working, a module. Or images of the outcomes etc....</p>

  • Sipke Schoorstra said

    <p>The post has been updated with a link to a sample module. If you install this module you should see a working demo where you will be able to create customers and addresses: <a href="https://lazyfielddemo.codeplex.com/releases/view/95579" rel="nofollow">https://lazyfielddemo.codeplex...</a></p>

  • Ali said

    <p>This was very helpful, great post. Thanks, </p>

    <p> I am however stuck on how to get associated Fields from a LazyFiled of Content Part, For instance I have added an image filed to the user part. How do I access that. any Ideas ?</p>

Comments have been disabled for this content.