Upgrading Custom Lava Components for v13

Although there were many changes needed to build in a new Rock Lava engine, the changes you'll need in order to be compatible with Rock v13 are very small.

TL;DR

Components

In many cases, you might just need to add:

using Rock.Lava;

And then include this above your Template.RegisterFilter(...):

LavaService.RegisterFilters( typeof( org.mydomain.MyCustomPluginsLavaFilters ) );

Custom Rock Blocks that use Render()

In many cases, you might just need to change how you get your Template from this:

var lavaTemplate = Template.Parse( GetAttributeValue( MY_LAVA_TEMPLATE ) );

to this:

using Rock.Lava;
...    
var parseResult = LavaService.ParseTemplate( GetAttributeValue( MY_LAVA_TEMPLATE ) );
var lavaTemplate = parseResult.Template;

Plugin Components

If you have plug-in code that uses Lava and DotLiquid objects prior to Rock v13, it will need to be modified to support the new Lava library implementation.

Rock v13 supports a number of different Lava processing modes, with the primary goal of easing the transition to the Lava library by providing backward compatibility and side-by-side verification and testing.

  • Fluid Mode - Fluid is the new rendering framework used by the Lava library, and it offers greater rendering speed and resource efficiency for your Rock environment. In this mode, Rock processes Lava templates using the framework which will become the default processor in future versions of Rock.

  • DotLiquid Mode - In this configuration, Rock continues to render Lava templates using the pre-v13 DotLiquid framework. This mode offers complete backward compatibility with existing custom code and plugins, and is available as a fallback if your custom code does not yet operate correctly with the new Lava library.

  • DotLiquid (Fluid Verification) Mode - As with DotLiquid mode, Lava output is rendered using the pre-v13 DotLiquid framework. However, the same Lava is also processed by the Fluid engine as a background process and any errors or discrepancies in the final output are recorded in the Rock Exception Log. This provides a means of testing your custom Lava for compatibility with the Fluid framework before selecting it as your Lava renderinf engine of choice.

    Operating in DotLiquid (Fluid Verification) Mode has some impact on Rock performance and is only recommended for transitional testing. You should switch to Fluid Mode as soon as you are satisfied that your site is compatible with the Lava library.

The Lava Engine Liquid Framework Global Attribute controls which engine Rock will use for processing Lava.

Side-By-Side Implementation

Adding support for the Lava library to your plug-in or custom code involves a number of changes. The most important thing to understand is that to be fully compatible with Rock v13 and allow your components to operate in both Fluid and DotLiquid modes, your custom code must provide support for both of these Lava implementations until the transition period is over and DotLiquid is no longer supported (likely v15).

For that reason, adding compatibility for the Lava library involves adding an alternate implementation to your code rather than modifying existing code.

Code that relies on Lava objects prior to Rock v13 will only continue to operate as expected if the Lava Engine is configured to use the DotLiquid framework. Support for legacy frameworks may be discontinued in future releases of Rock, so upgrading your Lava components is highly recommended.

Filter Registration

In previous versions of Rock, custom filter methods were registered as follows:

Template.RegisterFilter( typeof(MyFilterClass) );

In the Lava Library, filter registration is now accomplished using the Lava engine:

LavaService.RegisterFilters( typeof(MyFilterClass) );
You will want to register your filter for both engines during the transition period. Remember to add using Rock.Lava; to your usings.

Add Library References

Add a Reference to the Rock.Lava.Shared assembly to your project. This assembly holds all of the data objects and interfaces that are needed to interact with the Lava Engine.

Remove the reference to the DotLiquid.dll assembly from your project. Objects from the DotLiquid library should be replaced with the equivalent Lava library object, as shown in the following table.

Replace… With… Notes
DotLiquid.Context ILavaRenderContext New context instances are acquired using the LavaService.NewRenderContext method.
DotLiquid.DropBase LavaDataObject
DotLiquid.LiquidType LavaType This class attribute has the same effect.
Rock.Data.LavaInclude LavaVisible This member attribute has the same effect.
Rock.Data.LavaIgnore LavaHidden This member attribute has the same effect.
DotLiquid.Hash LavaDataDictionary Both of these objects are implementations of IDictionary<string, object>

Update Liquid Data Objects

Prior to v13, the properties of a class could be exposed to a Lava template by one of these methods:

  1. Registering the class as safe to render using the Template.RegisterSafeType method.
  2. Implementing the DotLiquid.ILiquidizable interface.
  3. Deriving from the Rock.Lava.RockDynamic base class.

Upgrade DotLiquid.ILiquidizable Types

The ILiquidizable interface has been replaced by ILavaDataDictionarySource. The purpose of this interface is the same – it signals that the class is capable of providing a data object that can be referenced in a Lava template.

Prior Version:

public class MyLavaClass : ILiquidizable
{
  public string Property1 { get; set; }
  public string Property2 { get; set; }
  
  public virtual object ToLiquid()
  {
    var dictionary = new Dictionary()
    {
      { "Property1", Property1 },
      { "Property2", Property2 }
    };
    return dictionary;
  }
}

Lava Library:

public class MyLavaClass : ILavaDataDictionarySource
{
  public string Property1 { get; set; }
  public string Property2 { get; set; }

  public virtual ILavaDataDictionary GetLavaDataDictionary()
  {
    var dictionary = new LavaDictionary()
    {
      { "Property1", Property1 },
      { "Property2", Property2 }
    };
    return dictionary;
  }
}

In the previous implementation, the ILiquidizable.ToLiquid function returned an untyped object, whereas ILavaDataDictionarySource.GetLavaDataDictionary returns a strongly-typed ILavaDataDictionary. This change is intended to add a measure of type-safety and prevent incompatible types being returned to the Lava engine. If the type you wish to return does not directly implement ILavaDataDictionary, it can be wrapped in a LavaDataObject to provide dynamic access to the wrapped object properties using .NET Reflection.

Some additional types have been added to the Lava library to assist with migrating current code. Each of these types directly implements the ILavaDataDictionary interface.

Upgrade DotLiquid.DropBase Types

The DotLiquid.DropBase abstract class has been replaced by the Rock.Lava.LavaDataSource class. The purpose of this class is the same – to allow customised storage and retrieval for the properties of a Lava-accessible data object.

Prior Version:

public class DataRowLava : DotLiquid.Drop
{
  private readonly DataRow _dataRow;

  public DataRowDrop( DataRow dataRow )
  {
    _dataRow = dataRow;
  }
  public override object BeforeMethod( string method )
  {
    if ( _dataRow.Table.Columns.Contains( method ) )
    {
      return _dataRow[method];
    }
    return null;
  }
}

Lava Library:

private class DataRowLava : LavaDataObject
{
  private readonly DataRow _dataRow;
  
  public DataRowDrop( DataRow dataRow )
  {
    _dataRow = dataRow;
  }

  protected override bool OnTryGetValue( string key, out object result )
  {
    if ( _dataRow.Table.Columns.Contains( key ) )
    {
      result = _dataRow[key];
      return true;
    }
    result = null;
    return false;
  }
}

Upgrading a Custom Filter

Filter Registration

In previous versions of Rock, custom filter methods were registered as follows:
Template.RegisterFilter( typeof(MyFilterClass) );
In the Lava Library, filter registration is now accomplished using the Lava engine:
LavaService.RegisterFilters( typeof(MyFilterClass) );

Filter Parameters

If the custom filter accepts a rendering context as a parameter, the parameter type must be changed from DotLiquid.Context to ILavaRenderContext.
Previous version:
public static string MyLavaFilter( DotLiquid.Context context, object input )
Lava Library:
public static string MyLavaFilter( ILavaRenderContext context, object input )

Upgrading a Custom Lava Block/Command

To upgrade a custom Lava command from a prior version of Rock to version 13, do the following:

Modify Usings

Remove the following instruction from the file header:
using DotLiquid;

Modify the OnStartup method

It is no longer necessary to manually register a Lava Block with the Lava Engine. The Lava Engine will automatically detect and register code components that are decorated with the IRockLavaBlock or IRockLavaTag interfaces.
Code that relates to manual registration of these elements can be removed from the OnStartup method:
Template.RegisterTag
Template.RegisterBlock
Template.RegisterShortcode<>
If your custom block contains non-registration related code that should be run on startup, you should implement the IRockStartup interface to ensure that this code is executed.

Replace the Context type

Replace all references to the DotLiquid.Context type with the ILavaRenderContext type.

Modify the Parse() method

Prior to v13, a custom Lava block could implement the following method to respond to the parse phase:
Parse(List<string> tokens)
During this method call, the underlying nodes in the parse tree were made accessible and could be modified through the DotLiquid.Block.NodeList collection. Manipulating the parse tree directly is no longer supported, and to indicate this changed behavior the method has been renamed:
OnParsed(List<string> tokens)
This change reflects a simplification of the parsing process that is necessary to add a consistent implementation across multiple Liquid rendering engines.
Prior to v13, the tokens collection also included the Lava block end tag as the last element. This element has been removed because it does not form part of the block content.

Custom Rock Blocks That Use Template.Parse() or .Render()

A custom Rock Block that fetches its own parser from the DotLiquid Template class should be adjusted to use the LavaService to get a template. To make it compatible with both engines through the transition period, you can implement a check to see which engine is currently running:

using Rock.Lava;
...
if ( LavaService.RockLiquidIsEnabled )
{
    var lavaTemplate = Template.Parse( GetAttributeValue( YOUR_LAVA_TEMPLATE ) );
    output = lavaTemplate.Render( Hash.FromDictionary( mergeFields ) );
}
else
{
    var parseResult = LavaService.ParseTemplate( GetAttributeValue( YOUR_LAVA_TEMPLATE ) );
    var lavaTemplate = parseResult.Template;
    output = lavaTemplate.Render( mergeFields );
}

Any code that simply uses the ResolveMergeFields() extension method will continue to work without modification.

var template = this.GetAttributeValue( MY_LAVA_TEMPLATE );
output = template.ResolveMergeFields( mergeFields );