EPiServer 9 - references to content

Recently I was working on a custom file search solution where I had to display all files + EPiServer pages that have a reference to those files.

To get started, I created a test page type like this:

[ContentType(GUID = "dec23890-1eb6-4364-80c0-71b62021c00c")]
public class TestPage : PageData
{
    public virtual XhtmlString XhtmlStringProperty { get; set; }

    [UIHint(UIHint.MediaFile)]
    public virtual ContentReference FileProperty { get; set; }

    public virtual ContentArea ContentAreaProperty { get; set; }
}

Uploaded some test file:

And created a few test pages:

IContentSoftLinkRepository

My initial idea was to use IContentSoftLinkRepository to get all references to test.pdf file:

var repository = ServiceLocator.Current.GetInstance<IContentSoftLinkRepository>();
var references = repository.Load(content.ContentLink, true).ToList();

I expected to get three references, but IContentSoftLinkRepository has returned only two. It failed to find pages that have a reference to a media file in ContentReference property.

I opened tblContentSoftlink table in SQL Server, and indeed, I found only two rows that contain my media file.

Then I tried to delete the file from EPiServer to get that nice warning popup, but this time, Episerver listed all references:

The conclusion was clear - EPiServer doesn't use tblContentSoftlink to keep track of the references.

ContentRepository

After some SQL profiling and playing with .NET decompilers, I discovered that EPiServer executes editDeletePageCheck stored procedure under the hood, and so does IContentRepository.GetReferencesToContent().

var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
var references = repository.GetReferencesToContent(content.ContentLink, true).ToList();

Note: GetReferencesToContent method won't return content inside Trash, which is a big plus.

The code

In my search results, I only want to display published pages, and pages that have a template / URL (I want to skip container pages).

Since ContentRepository.GetReferencesToContent() can return blocks, I had to show all pages where this block is used.

Here's the final code:

public class ContentUtils
{
    private readonly IContentRepository _contentRepository;

    public ContentUtils(IContentRepository contentRepository)
    {
        _contentRepository = contentRepository;
    }

    public List<PageData> GetPageReferencesToContent(ContentReference contentReference)
    {
        var list = GetPagesRecursively(contentReference)
            .Filter(new FilterTemplate()) // exclude container pages
            .Filter(new FilterPublished()) // exclude unpublished pages
            .Distinct()
            .ToList();

        return list;
    }

    private IEnumerable<PageData> GetPagesRecursively(ContentReference contentReference)
    {
        var references = _contentRepository.GetReferencesToContent(contentReference, true).ToList();
        foreach (var reference in references)
        {
            var content = _contentRepository.Get<IContent>(reference.OwnerID);

            // if content is PageData, return it
            var page = content as PageData;
            if (page != null)
            {
                yield return page;
            }

            // if content is BlockData, return all pages where this block is used
            var block = content as BlockData;
            if (block != null)
            {
                foreach (var x in GetPagesRecursively(content.ContentLink))
                {
                    yield return x;
                }
            }
        }
    }
}

Usage:

var contentUtils = ServiceLocator.Current.GetInstance<ContentUtils>();
var pages = contentUtils.GetPageReferencesToContent(new ContentReference(87));

 

comments powered by Disqus