AI OnAI Off
Your class look a bit suspicious to me.
JobFilterContentApiModelFilter should implement IContentApiModelFilter and be injected, eg
// NO ATTRIBUTES HERE
public class JobFilterContentApiModelFilter : IContentApiModelFilter
{
private readonly IContentLoader _contentLoader;
private readonly ContentConvertingService _contentConvertingService; // or possibly IContentConverterResolver?
private readonly IJobPageFinder _jobPageFinder;
private readonly IServiceProvider _services;
public JobFilterContentApiModelFilter(
IContentLoader contentLoader,
IJobPageFinder jobPageFinder,
ContentConvertingService contentConvertingService
IServiceProvider services)
{
_contentLoader = contentLoader;
_contentConvertingService = contentConvertingService;
_jobPageFinder = jobPageFinder;
_services = services;
}
}
Inject in startup
services.AddSingleton<IContentApiModelFilter, JobFilterContentApiModelFilter>();
And work your way from there.
I found the answer myself, thanks @Eric for your assistance. The root issue is that there is a circular dependency between ContentApiModelFilter and ContentConvertingService. The latter is using the former to filter the contentApiModel at the end. But my implementation needs the ContentConvertingService to get the models of each JobPage.
GetAllJobPageModels
:[ServiceConfiguration(typeof(IContentApiModelFilter), Lifecycle = ServiceInstanceScope.Singleton)]
public class JobFilterContentApiModelFilter : IContentApiModelFilter
{
#region constants
private const string _PROPERTYNAME_JOBS = "Jobs";
private const string _EXPAND_ALL_PROPERTIES = "*";
#endregion
#region fields
private readonly IContentLoader _contentLoader;
private readonly Lazy<ContentConvertingService> _lazyContentConvertingService;
private readonly IJobPageFinder _jobPageFinder;
#endregion
#region constructor
/// <summary>
/// Creates a new object of type <see cref="JobFilterContentApiModelFilter"/>.
/// Is called automatically by the system.
/// </summary>
/// <param name="contentLoader">The <see cref="IContentLoader"/> instance to load content information</param>
/// <param name="jobPageFinder">The <see cref="IJobPageFinder"/> instance to resolve all jobs of the site's job-portal</param>
/// <param name="services">Service to get an instance of <see cref="Lazy<ContentConvertingService>"/> to resolve all jobs of the site's job-portal</param>
public JobFilterContentApiModelFilter(
IContentLoader contentLoader,
IJobPageFinder jobPageFinder,
IServiceProvider services)
{
_contentLoader = contentLoader;
_lazyContentConvertingService =
new Lazy<ContentConvertingService>(services.GetRequiredService<ContentConvertingService>);
_jobPageFinder = jobPageFinder;
}
#endregion
#region properties
Type IContentApiModelFilter.HandledContentApiModel => typeof(ContentApiModel);
#endregion
#region Filter-method
/// <summary>
/// The main filter method. In this method, we check if the content is a <see cref="JobFilterBlock"/>.
/// In that case all existing job pages are added to the contentApiModel.
/// The filtering is done on client-side.
/// </summary>
/// <param name="contentApiModel">
/// The current <see cref="ContentApiModel"/> we'll be adding the job-pages to
/// </param>
/// <param name="converterContext">The converter context with information about the current content</param>
public void Filter(ContentApiModel contentApiModel, ConverterContext converterContext)
{
if (!_contentLoader.TryGet(converterContext.ContentReference, out IContent? content)
|| content is not JobFilterBlock jobFilter)
{
return;
}
// this is a JobFilterBlock, add all job-pages models to the contentApiModel
List<object> allJobPageModels = GetAllJobPageModels(converterContext);
if (!allJobPageModels.Any())
{
return;
}
contentApiModel.Properties[_PROPERTYNAME_JOBS] = allJobPageModels;
}
#endregion
#region private methods
/// <param name="converterContext"></param>
/// <returns>A List containing all job-pages' api-models</returns>
private List<object> GetAllJobPageModels(ConverterContext converterContext)
{
IList<JobPage> allJobs = _jobPageFinder.GetAllJobPages();
List<object> jobModels = new(allJobs.Count);
jobModels.AddRange(allJobs.Select(GetJobApiModel));
return jobModels;
object GetJobApiModel(JobPage job)
{
return _lazyContentConvertingService.Value.Convert(job, GetJobContext(job));
}
ConverterContext GetJobContext(JobPage job)
{
return new ConverterContext(
job.ContentLink,
converterContext.Language,
converterContext.Options,
converterContext.ContextMode,
null,
_EXPAND_ALL_PROPERTIES,
converterContext.ExcludePersonalizedContent
);
}
}
#endregion
}
Hello,
I need to modify the content api model if a page contains a spefic "Job filter"-block. In that case all jobs on the site must be added to the model under the meta data of that block.
So my idea was to create a custom
ContentApiModelFilter<ContentApiModel>
and check if the current content is of typeJobFilterBlock
. If so, i add as a new property "Jobs" the content api models of all jobs. But the problem:ContentConvertingService
this is a circular dependency becauseContentConvertingService
uses(calls) the filters at the end. So i would create an infinite loop.What is the corect way? Following would run(Lazy<ContentConvertingService> to avoid already an exception in dependency injection), but indeed crashes because it is an infinite loop: