I've been tinkering with some pipeline modules for the Statiq generation engine that I use to render this blog, and I bumped into a fun mistake in my code which confused me for a while. To help me next time I'm doing this sort of modification, this seemed worth documenting - even if it is a bit of a silly mistake...
I was working on a module which needed to generate some custom data for specific content types in the input to my Statiq site. So I created a new Module and typed
override
and let the autocomplete generate the main method for this class:
public class CustomIndexModule : ParallelModule { protected override Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context) { return base.ExecuteInputAsync(input, context); } }
And with that in place I started to write some logic. Most of it is unimportant to this issue - but the key lines were basically:
public class CustomIndexModule : ParallelModule { protected override Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context) { if(!input.Destination.FullPath.StartsWith("myFolder/")) { return base.ExecuteInputAsync(input, context); } // // logic to generate the index data // } }
So (I thought) if the particular document isn't in the right folder, this module will skip over it and do nothing. And I added this module to the pipeline for the site.
But once this was in place I started to see some odd issues. Running the generator now threw an unexpected error for a page which had been working fine beforehand:
[ERRO] Archives/PostProcess » ExecuteSwitch » RenderContentPostProcessTemplates » ExecuteIf » ExecuteIf » RenderRazor » [C:/Users/Jeremy/source/repos/blog.jermdavis.dev/StatiqGenerator/theme/input/mvp.cshtml => mvp.html] Object reference not set to an instance of an object.
And commenting out the new module made this go away immediately.
I spent a bit of time fiddling here, trying to work out what had gone wrong - but the only other thing I noticed was that some files were missing from the output - including the one that was mentioned in the message above.
Having dug around for rather too long, I realised that I should look back at previous code I'd written for modules. And in doing that I had a bit of a revelation. In the code I'd written the "do nothing" behaviour was to call the
base.ExecuteInputAsync()
method as that's what the autocomplete had created with the empty method. (That's the default behaviour for overriding a C# method - call its base) But my previous code had called
input.Yield()
instead, driven by what's in the Statiq documentation. And changing the code to:
public class CustomIndexModule : ParallelModule { protected async override Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context) { if(!input.Destination.FullPath.StartsWith("myFolder/")) { return input.Yield(); } // rest of the logic } }
resolved the issue. So what was up? Well digging into the code that underlies the base
Module
definition you see this:
protected virtual Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context) => Task.FromResult<IEnumerable<IDocument>>(null);
The overall result of calling the base method for
ExecuteInputAsync()
is to return
null
- so my code above wasn't keeping the current
input
document, but was discarding it.
Hence the issue I was seeing where files were not being generated by the build. Instead of being passed through unprocessed, the documents that didn't need the custom index data were being discarded.
Hopefully this post will help me not make that mistake again. And it's a useful reminder that generally "pipeline" style code needs a different approach to doing nothing when it receives an input. You generally need to make sure you pass the input back out again rather than actually doing nothing at all...
↑ Back to top