Sitecoreはハブについてほとんどカバーしていませんが、この非常に機能的な(そして高価な)CMSは、余裕がある人には非常に人気があります。 ただし、sitecoreでサイトを開発している(特にサポートしている)人は、テンプレートを変更することの難しさを訴えます。 そのため、テンプレートまたは1つのフィールドの名前を変更するだけでは、予測不可能で、最も重要なこととして、サイト内の違反の診断と修正が困難になる可能性があります。 そして、彼らは数ヶ月後にしか出られません。 さらに、標準のSiteCore FieldRendererを使用すると、レイアウトの制御が難しくなります。これは、このケースでは重要でした。
なぜ自転車なのか?
テンプレートに基づいてクラスを生成するソリューション( trac.sitecore.net/CompiledDomainModelなど )がありますが、それらは使用するのにあまり便利ではなく、テンプレート構造、フィールド名へのバインドを排除しません。 前述のCompiledDomainModelでは、変更後にすべてのモデルを再生成する必要があります。 また、共同開発(生成されたコードの絶え間ない競合)にはあまり適さず、すべてのテンプレートに一意の名前が必要であり、テンプレートおよびID-shkへの途中で結び付けられ、1つのファイルに巨大なコードを生成します(プロジェクトの1つでは60,000行を超えており、開いていますVSではそれほど高速ではありませんでした)。
私たちのチームは、既存のサイトをサポートした経験に基づいて、sitecore 6.3の新しいサイトを開発するのに十分なほど幸運でした。 コンテンツサイトについて話していることをすぐに強調する必要があります。 興味深い機能は管理パネルでのみ必要であり、サイトコアとの直接的な接続はほとんどありません。
次に、ホイールの形状について
サブレイアウトにサイトコアへのリンクを残し、テンプレートとフィールドの厳密な型指定を固定し、フィールドの名前を単一の予測可能な場所にのみ保存することが決定されました。
テンプレートのすべてのラッパークラスの基礎は、テンプレートクラスです。その主なタスクは、既存のすべてのアイテムテンプレートをチェックし、宣言されたテンプレートで名前を確認することです。 クラスとテンプレートの関係では、DataContract属性が使用されます。
注:以下では、主なアイデアと読みやすさを伝えるためにコードを短縮しています
[DataContract(Name = "Base text page")] public class BaseTextPage : Template {...}
public class Template { private readonly Item item; public Template(Item item) { var missedTemplates = GetMissedTemplates(item, this.GetType()); // DataContract Item-. if (missedTemplates.Any()) { ... throw new InvalidDataException("Item is not of required template”); // } this.item = item; } … }
同じTemplateクラスには、テンプレートフィールドにアクセスするための便利な機能がいくつかあります。
protected T GetField<T>(string name, T @default = default(T)) { var dataType = typeof(T); var field = this.Item.Fields[name]; … // : if (dataType == typeof(string)) { if (string.IsNullOrEmpty(field.Value)) { return @default; } return (T)(object)field.Value; } if (dataType == typeof(LinkField)) { return (T)(object)new LinkField(field); } if (dataType == typeof(ImageField)) { return (T)(object)new ImageField(field); } … //etc for all field types } protected T GetFromField<T>(string name) where T : Template { var link = this.GetField<ReferenceField>(name); if (link != null && link.TargetItem != null) { return (T)Activator.CreateInstance(typeof(T), link.TargetItem); } return null; } protected T GetFromParent<T>() where T : Template { if (this.Item == null || this.Item.Parent == null) { return default(T); } return (T)Activator.CreateInstance(typeof(T), this.Item.Parent); }
次のステップは、フィールドにアクセスすることです。 サイト-サイトのよく知られている問題は、サイト-サイトをサポートすることです。フィールド名はプロジェクト全体にscatteredしみなく散らばっています。 私たちのタスクは、プロジェクト内にただ1つのフィールド名を持つことです。 ここでも、標準の属性、現在はDataMemberを使用しています。
[DataContract(Name = "Base text page")] public class BaseTextPage : Template { [DataMember(Name = "Big text content")] public string Text { get { return this.Item[this.GetFieldName(x => x.QuestionText)]; } } [DataMember(Name = "Logo Image")] public string LogoImage { get { return this.GetField<ImageField>(this.GetFieldName(x => x.BigImage)).GetMediaUrl(); } } … }
ここで最も重要なのは、次の形式の拡張メソッドとして宣言されたGetFieldName関数です。
private static readonly Dictionary<string, string> fieldNameCache = new Dictionary<string, string>(); public static string GetFieldName<T, TResult>(this T obj, Expression<Func<T, TResult>> memberExpression) where T : class { if (obj == null) { throw new ArgumentNullException("obj"); } var member = memberExpression.ToMember(); if (member.MemberType != MemberTypes.Property) { throw new ArgumentException("Not a property access", "memberExpression"); } var fieldCahceKey = typeof(T).Name + member.Name; if (fieldNameCache.ContainsKey(fieldCahceKey)) { return fieldNameCache[fieldCahceKey]; } var fieldName = typeof(T) .GetProperty(member.Name) .GetCustomAttributes(typeof(DataMemberAttribute), true) .Cast<DataMemberAttribute>() .Select(curr => curr.Name) .FirstOrDefault(); if (string.IsNullOrEmpty(fieldName)) { return null; } fieldNameCache[fieldCahceKey] = fieldName; return fieldName; } private static MemberInfo ToMember<TMapping, TReturn>( this Expression<Func<TMapping, TReturn>> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var expression = propertyExpression.Body; if (expression.NodeType == ExpressionType.MemberAccess) { var memberExpression = expression as MemberExpression; if (memberExpression != null) { return memberExpression.Member; } } throw new ArgumentException("Not a member access", "propertyExpression"); }
この時点で、次のように記述できます。
BaseTextPage page = new BaseTextPage(Sitecore.Context.Item); var text = page.Text; var imageUrl = page.LogoImage;
テンプレートがBaseTextPageクラスに適している場合、現在のItemの「Big text content」/「Logo image」フィールドからデータを取得します。
次に、他のすべてのテンプレートのベースとなるテンプレートのラッパーを作成します。 少なくとも「Standartテンプレート」になりますが、通常はもっと便利なことをした方が良いでしょう。 例えば
[DataContract(Name = "Base page")] public class BasePage : Template { [DataMember(Name = "Show in menu")] public bool ShowInMenu { get { return this.Item[this.GetFieldName(x => x.ShowInMenu)].GetBoolValue(); } } [DataMember(Name = "Page title")] public string Title { get { return this.Item[this.GetFieldName(x => x.Title)]; } } }
これをすべてSublayoutで実装します。
public class BaseSublayout<T> : UserControl where T : BasePage { protected virtual T Model { get { return (T)Activator.CreateInstance(typeof(T), Sitecore.Context.Item); } } } public partial class ConcreteTextPage: BaseSublayout<MyProject.ORM.Content.ConcreteMapping> { protected void Page_Load(object sender, EventArgs e) { var smthUsefull = this.Model.HeaderText; } }
この瞬間から、.aspxファイルの内容はASP.MVCの内容に似始めます。 利便性を
<h1><%= this.Model.Header %></h1> <%= HtmlHelper.RenderImage(this.Model.SomeEntity.MainImage) %> <% foreach (var link in this.Model.SelectedLinks) { %> <%= HtmlHelper.Anchor(link.Url, link.Text) %> <% } %>
アプローチの利点:
+プロパティの形式で、手元のコンテキスト項目のすべての必要なフィールド
+コンテキストアイテムには常に正しいテンプレートがあります
+ Sitecoreのすべてのデータチェック標準は1か所で行われます
+同様のラッパーが記述されているサイト設定への集中アクセス
+クリーン(最小限、欠落)ページコード
+完全なレイアウト制御
短所
-すべての手のマッピング
-フィールド/テンプレート名を抽出するための費用
この記事がSitecore開発者に役立つことを願っています。