Apply when working with Sitecore 10.x projects, Helix architecture, MVC renderings, or Sitecore APIs
Applies Sitecore 10.x Helix architecture, dependency injection, and content search patterns.
/plugin marketplace add twofoldtech-dakota/claude-marketplace/plugin install twofoldtech-dakota-sitecore-classic-analyzer-plugins-sitecore-classic-analyzer@twofoldtech-dakota/claude-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Foundation Layer
Feature Layer
Project Layer
Project → Feature → Foundation
Features must NEVER reference other Features. If shared logic is needed, extract to Foundation.
src/
├── Foundation/
│ └── {Module}/
│ ├── code/
│ │ ├── DependencyInjection/
│ │ ├── Services/
│ │ └── Foundation.{Module}.csproj
│ └── serialization/
│ └── {Module}.module.json
├── Feature/
│ └── {Module}/
│ ├── code/
│ │ ├── Controllers/
│ │ ├── Models/
│ │ ├── Services/
│ │ ├── Repositories/
│ │ └── Feature.{Module}.csproj
│ └── serialization/
│ ├── Templates/
│ ├── Renderings/
│ └── {Module}.module.json
└── Project/
└── {Site}/
├── code/
│ ├── Layouts/
│ └── Project.{Site}.csproj
└── serialization/
├── Content/
└── {Site}.module.json
using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;
public class RegisterDependencies : IServicesConfigurator
{
public void Configure(IServiceCollection services)
{
// Transient: New instance each time
services.AddTransient<ISearchService, SearchService>();
// Scoped: One instance per HTTP request
services.AddScoped<INavigationService, NavigationService>();
// Singleton: One instance for application lifetime
services.AddSingleton<ICacheService, CacheService>();
}
}
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<services>
<configurator type="Foundation.DI.RegisterDependencies, Foundation.DI" />
</services>
</sitecore>
</configuration>
public class NavigationController : Controller
{
private readonly INavigationService _navigationService;
private readonly ILogger<NavigationController> _logger;
public NavigationController(
INavigationService navigationService,
ILogger<NavigationController> logger)
{
_navigationService = navigationService;
_logger = logger;
}
public ActionResult Index()
{
var datasource = RenderingContext.Current.Rendering.Item;
if (datasource == null)
{
_logger.LogWarning("Navigation datasource is null");
return new EmptyResult();
}
var model = _navigationService.GetNavigation(datasource);
return View(model);
}
}
public class SearchRepository : ISearchRepository
{
private readonly ISearchIndex _index;
public SearchRepository(ISearchIndexResolver indexResolver)
{
_index = indexResolver.GetIndex("sitecore_web_index");
}
public IEnumerable<ArticleSearchResult> GetArticles(
ID templateId,
string language,
int maxResults = 100)
{
using (var context = _index.CreateSearchContext())
{
return context.GetQueryable<ArticleSearchResult>()
.Where(x => x.TemplateId == templateId)
.Where(x => x.Language == language)
.OrderByDescending(x => x.Created)
.Take(maxResults)
.ToList();
}
}
}
// BAD - Tree traversal, O(n) where n = tree size
var items = item.Axes.SelectItems(".//*[@@templateid='{GUID}']");
// BAD - Even "fast:" queries traverse the tree
var items = Database.SelectItems("fast:/sitecore/content//*");
// GOOD - Use Content Search instead
var items = searchContext.GetQueryable<SearchResultItem>()
.Where(x => x.TemplateId == templateId)
.ToList();
// BAD - Loads entire subtree into memory
var allItems = rootItem.GetDescendants().ToList();
// GOOD - Use Content Search with path filter
var items = searchContext.GetQueryable<SearchResultItem>()
.Where(x => x.Paths.Contains(rootItem.ID))
.Take(1000)
.ToList();
Configure in rendering item or config:
<r>
<d id="{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}">
<r uid="{RENDERING-UID}"
s:caching="1"
s:varybydata="1"
s:varybydevice="0"
s:varybylogin="0"
s:varybyuser="0"
s:varybyquerystirng="0"
s:timeout="01:00:00" />
</d>
</r>
public class NavigationCache : CustomCache
{
public NavigationCache(string name, long maxSize)
: base(name, maxSize)
{
}
public NavigationModel Get(string cacheKey)
{
return GetObject<NavigationModel>(cacheKey);
}
public void Set(string cacheKey, NavigationModel model, TimeSpan duration)
{
SetObject(cacheKey, model, duration);
}
}
Register cache:
public class RegisterCaches : IServicesConfigurator
{
public void Configure(IServiceCollection services)
{
services.AddSingleton<NavigationCache>(provider =>
new NavigationCache("NavigationCache", StringUtil.ParseSizeString("50MB")));
}
}
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<settings>
<setting name="MySetting" value="MyValue" />
</settings>
</sitecore>
</configuration>
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"
xmlns:env="http://www.sitecore.net/xmlconfig/env/">
<sitecore>
<settings>
<!-- Only in Production -->
<setting name="Analytics.Enabled" env:require="Production">
<patch:attribute name="value">true</patch:attribute>
</setting>
<!-- Only in Development -->
<setting name="Analytics.Enabled" env:require="Development">
<patch:attribute name="value">false</patch:attribute>
</setting>
</settings>
</sitecore>
</configuration>
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"
xmlns:role="http://www.sitecore.net/xmlconfig/role/">
<sitecore>
<settings>
<!-- Only on Content Management servers -->
<setting name="Preview.Enabled" role:require="ContentManagement">
<patch:attribute name="value">true</patch:attribute>
</setting>
<!-- Only on Content Delivery servers -->
<setting name="Preview.Enabled" role:require="ContentDelivery">
<patch:attribute name="value">false</patch:attribute>
</setting>
</settings>
</sitecore>
</configuration>
public class EnrichContextProcessor : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
// Early exit for efficiency
if (Context.Item == null) return;
if (Context.Site == null) return;
if (Context.PageMode.IsExperienceEditor) return;
// Processor logic here
EnrichContext();
}
private void EnrichContext()
{
// Custom enrichment logic
}
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<httpRequestBegin>
<processor type="Feature.Context.EnrichContextProcessor, Feature.Context"
patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
</httpRequestBegin>
</pipelines>
</sitecore>
</configuration>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<configuration>
<indexes>
<index id="custom_articles_index">
<configuration ref="contentSearch/indexConfigurations/defaultSolrIndexConfiguration">
<documentOptions>
<fields hint="raw:AddComputedIndexField">
<field fieldName="article_author" returnType="string">
Feature.Articles.ComputedFields.ArticleAuthor, Feature.Articles
</field>
<field fieldName="article_category" returnType="stringCollection">
Feature.Articles.ComputedFields.ArticleCategories, Feature.Articles
</field>
</fields>
</documentOptions>
</configuration>
</index>
</indexes>
</configuration>
</contentSearch>
</sitecore>
</configuration>
public class ArticleAuthor : IComputedIndexField
{
public string FieldName { get; set; }
public string ReturnType { get; set; }
public object ComputeFieldValue(IIndexable indexable)
{
var item = (indexable as SitecoreIndexableItem)?.Item;
if (item == null) return null;
var authorItem = item.GetReferenceField("Author")?.TargetItem;
return authorItem?["Name"];
}
}
{
"namespace": "Feature.Navigation",
"items": {
"includes": [
{
"name": "templates",
"path": "/sitecore/templates/Feature/Navigation",
"allowedPushOperations": "CreateUpdateAndDelete"
},
{
"name": "renderings",
"path": "/sitecore/layout/Renderings/Feature/Navigation",
"allowedPushOperations": "CreateUpdateAndDelete"
},
{
"name": "settings",
"path": "/sitecore/system/Settings/Feature/Navigation",
"allowedPushOperations": "CreateUpdateAndDelete"
}
]
}
}
# Pull items from Sitecore to disk
dotnet sitecore ser pull
# Push items from disk to Sitecore
dotnet sitecore ser push
# Validate serialization
dotnet sitecore ser validate
[SitecoreType(TemplateId = "{GUID}", AutoMap = true)]
public class ArticlePage
{
[SitecoreId]
public virtual Guid Id { get; set; }
[SitecoreField("Title")]
public virtual string Title { get; set; }
[SitecoreField("Content")]
public virtual string Content { get; set; }
[SitecoreField("Author")]
public virtual Author Author { get; set; }
[SitecoreChildren]
public virtual IEnumerable<RelatedArticle> RelatedArticles { get; set; }
}
public class ArticleService : IArticleService
{
private readonly ISitecoreService _sitecoreService;
public ArticleService(ISitecoreService sitecoreService)
{
_sitecoreService = sitecoreService;
}
public ArticlePage GetArticle(Guid id)
{
return _sitecoreService.GetItem<ArticlePage>(id);
}
}
// BAD - N+1 queries with lazy loading
foreach (var article in articles)
{
var author = article.Author; // Lazy load for each article
}
// GOOD - Eager load or use Content Search
var articleIds = articles.Select(a => a.Id).ToList();
var authors = searchContext.GetQueryable<AuthorSearchResult>()
.Where(x => articleIds.Contains(x.ArticleId))
.ToList();