1751068595
2025-06-26 16:11:00
先日、マルチサイトのグローバル資産の検索に関する質問があり、サポートケースがありました。これはその調査の結果です。
このコードはそのまま提供されますが、フィードバックがある場合はお知らせください。
Episerver(optimizely)CMSで、 グローバル資産– グローバルアセットフォルダーに保存されている画像、ドキュメント、ブロックなど、特定のサイトまたは言語に結び付けられていません。
しかし、参照するコンテンツは通常です。
あなたは一般的な問題に気づいたかもしれません:
検索を実装するとき サイトa、参照されている資産 のみ コンテンツによって サイトb それでも結果に表示され、その逆も同様です。それは多分私たちが望むものではないかもしれません。
あなたが持っているとしましょう:
-
使用されている資産 独占的に の上 サイトa、 または
-
資産 共有 間 サイトa そして サイトb。
ユーザーが検索するとき サイトa、関連する結果が必要です のみ そのサイトに対して、そのサイトのコンテンツが参照するグローバル資産を含む。同様に、資産が両方のサイト間で共有されている場合、両方の結果セットに表示されるはずです。
これをサポートするために、あなた インデックス作成プロセス グローバル資産を作成する必要があります どのサイトと言語を認識しています それらを参照してください。
これが方法です
使用している場合 UnifiedSearch、サイトIDと言語でのこのフィルタリングは通常、次のように箱から出して動作します。
* filteroncurrentsite() / filterSite()
* filterforvisitor() / publidedinlanguage()
また、入力された検索の場合、これらのフィルターを簡単に適用できます。
初期化モジュールを更新します
これをグローバル資産で機能させるために、ビルトインフィールドを除外し、独自のロジックに置き換えることにより、SiteID()およびPublidedInlanguage()のデフォルトのインデックス作成動作をオーバーライドします。
SearchClient.Instance.Conventions.ForInstancesOf().ExcludeField(x => x.SiteId());
SearchClient.Instance.Conventions.ForInstancesOf().IncludeField(x => x.SiteId(true));
SearchClient.Instance.Conventions.ForInstancesOf().ExcludeField(x => x.PublishedInLanguage());
SearchClient.Instance.Conventions.ForInstancesOf().IncludeField(x => x.PublishedInLanguage(true));
拡張メソッド
静的クラスのカスタムロジックを定義して、どのクラスを決定しますか サイト そして 言語 特定のグローバル資産を参照してください。
using EPiServer.Find.Cms;
using EPiServer.Framework.Cache;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using EPiServer.Find.Helpers;
public static class GlobalAssetsExtensions
{
private static readonly Lazy _siteDefinitionResolver =
new(() => ServiceLocator.Current.GetInstance());
private static readonly Lazy _contentLoader =
new(() => ServiceLocator.Current.GetInstance());
private static readonly Lazy _contentRepository =
new(() => ServiceLocator.Current.GetInstance());
private static readonly Lazy _versionContentRepository =
new(() => ServiceLocator.Current.GetInstance());
private static readonly Lazy _objectCache =
new(() => ServiceLocator.Current.GetInstance());
private static readonly Lazy _contentCacheKeyCreator =
new(() => ServiceLocator.Current.GetInstance());
public static IEnumerable SiteId(this IContentMedia content, bool foobar)
{
content.ValidateNotNullArgument(nameof(content));
var siteDefinitionResolver = _siteDefinitionResolver.Value;
var contentLoader = _contentLoader.Value;
var contentRepository = _contentRepository.Value;
var objectCache = _objectCache.Value;
var contentCacheKeyCreator = _contentCacheKeyCreator.Value;
if (siteDefinitionResolver.GetByContent(content.ContentLink, false) == null)
{
if (ContentReference.IsNullOrEmpty(content.ParentLink) ||
!contentLoader.TryGet(content.ParentLink, out _))
{
return Enumerable.Empty();
}
if (contentLoader.GetAncestors(content.ContentLink)
.Any(x => x.ContentLink == ContentReference.GlobalBlockFolder))
{
var cacheKey = $"SiteIds:{content.ContentLink.ID}";
var cachedSiteIds = objectCache.Get(cacheKey) as IEnumerable;
if (cachedSiteIds != null)
{
return cachedSiteIds;
}
var contentRefs = new HashSet();
foreach (var link in contentRepository.GetReferencesToContent(content.ContentLink, false))
{
if (!ContentReference.IsNullOrEmpty(link.OwnerID))
{
contentRefs.Add(link.OwnerID);
}
}
var siteIds = new HashSet();
foreach (var contentRef in contentRefs)
{
var site = siteDefinitionResolver.GetByContent(contentRef, false);
if (site != null)
{
siteIds.Add(site.Id.ToString());
}
}
objectCache.Insert(cacheKey, siteIds, new CacheEvictionPolicy(
TimeSpan.FromMinutes(120), CacheTimeoutType.Sliding,
[contentCacheKeyCreator.VersionKey]));
return siteIds;
}
}
return [content.SiteId()];
}
public static Dictionary PublishedInLanguage(this IContentMedia content, bool foobar)
{
content.ValidateNotNullArgument(nameof(content));
if (ContentReference.IsNullOrEmpty(content?.ContentLink))
{
return null;
}
var contentLoader = _contentLoader.Value;
var contentRepository = _contentRepository.Value;
var versionRepository = _versionContentRepository.Value;
var objectCache = _objectCache.Value;
var contentCacheKeyCreator = _contentCacheKeyCreator.Value;
if (contentLoader.GetAncestors(content.ContentLink)
.Any(x => x.ContentLink == ContentReference.GlobalBlockFolder))
{
// Should we need to check whether entire ancestor chain are resolvable?
var cacheKey = $"Languages:{content.ContentLink.ID}";
var cachedLanguages = objectCache.Get(cacheKey) as Dictionary;
if (cachedLanguages != null)
{
return cachedLanguages;
}
var contentRefs = new HashSet();
foreach (var link in contentRepository.GetReferencesToContent(content.ContentLink, false))
{
if (!ContentReference.IsNullOrEmpty(link.OwnerID))
{
contentRefs.Add(link.OwnerID);
}
}
var languages = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var contentRef in contentRefs)
{
var publishedLanguages = versionRepository
.ListPublished(contentRef)
.Select(x => x.LanguageBranch);
foreach (var lang in publishedLanguages)
{
if (contentLoader.TryGet(contentRef, new LanguageSelector(lang), out var contentInLang))
{
var langStatus = contentInLang.PublishedInLanguage();
if (langStatus != null)
{
foreach (var kvp in langStatus)
{
languages.TryAdd(kvp.Key, kvp.Value);
}
}
}
}
}
objectCache.Insert(cacheKey, languages, new CacheEvictionPolicy(
TimeSpan.FromMinutes(120), CacheTimeoutType.Sliding,
[contentCacheKeyCreator.VersionKey]));
return languages;
}
return content.PublishedInLanguage();
}
}
インデックスでどのように見えるか
さまざまなサイトや言語で参照されているグローバル資産を再インド化した後、このような濃縮メタデータが含まれるようになりました
"PublishedInLanguage": { "sv": { "StartPublish$$date": "2017-02-22T11:24:00Z" },
"fi": { "StartPublish$$date": "2025-06-18T11:03:00Z" },
"en": { "StartPublish$$date": "2012-10-04T11:53:00Z" },
"en-GB": { "StartPublish$$date": "2025-05-21T06:24:00Z" } },
"SiteId": [ "site-a-guid", "site-b-guid" ]
2025年6月26日
#インデックス作成時にグローバルアセットサイトと言語を認識します