The CompiledDomainModel module helps you create domain model classes based on your Sitecore templates and is capable of validating the model with Sitecore data at any given moment. It is useful for 2 situations in particular:
Some of the key features are listed here (for a more comprehensive list, check the release notes):
This module was created by Robin Hermanussen and released under the GPLv3 license.
All programmers make mistakes sometimes. And when they do, they want to know about it ASAP. That's why programmers like strong typing. It allows them to know about problems compile-time.
In Sitecore, restrictions on items are defined using templates. The templates define fields with different types and content editors who work with items based on those templates are restricted to using those fields.
One of the problems that I run into as a Sitecore developer is that templates and template fields are sometimes changed, which can cause code that makes assumptions about the structure of Sitecore items to fail. That's why I often have to write code that checks these assumptions at runtime and gives relevant feedback when the assumptions are incorrect.
These checks generally clutter up my code and distract me from the relevant tasks I'm working on. Even worse, I sometimes forget to do the checks in code. And when something actually fails at runtime, it is often too late; the code is already being tested or in production. Extensive regression testing can aid in finding these problems, but I'd really like to know about these problems at an early stage.
And that's what this module can help you with. You can generate code to help you navigate Sitecore items based on the restrictions in the templates. Checking the assumptions becomes unnecessary, because the module can validate them and provide you with extensive feedback if any problems do arise.
Because the generated code uses partial classes, it is quite easy to implement specific behavior for items with specific templates. It's great for coding business logic.
The module is designed so that there is no entanglement with your templates or code. In fact, you can even make sure that there are no dependencies with the module at all. If you check the "Remove dependencies" checkbox on the settings item then after you have created your domain model, you could remove the entire module. You would also lose the validation abilities, so this is not recommended.
Possible reasons for using this module:
Possible reasons for NOT using this module:
To install the module, you can follow the same procedure as for any Sitecore module. The basic steps:
After you've installed the module, you can configure it by going to /sitecore/system/Modules/CompiledDomainModel/Settings.
The settings:
Next, you will need to add sets to your settings. A set is a settings item that contains configuration for a group of items that will be used for the code generation.
There are 2 types of sets. The first is the DomainObjectSet. It can be used to configure for what templates to generate typed wrapper classes.
The DomainObjectSet:
The second type of set that is supported is the FixedPathSet. It can be used to select paths in the content tree that should never change. The generated code relies on this, so the validation checks to see if everything is right where it belongs. The fixed paths are useful for defining what items are used for things like global configuration. The generated code can then be used to access the items using a syntax that corresponds to the Sitecore content tree structure (e.g. MyDomainModel.Content.Home.Configuration.Theme.FixedLocation.DomainObject).
There are also relative fixed paths. They can be used to define a fixed structure. That structure can later be used to get quick access to parts of the content tree that are not fixed, but that do have that same structure. This can be useful, for example, in multi-site environments where a part of the structure of each site is always the same. It is recommended to use Sitecore branches to define these structures.
The FixedPathSet:
After defining this configuration, you are ready to generate code. Any changes made to this configuration, to the templates in the structure or to the fixed paths in the content tree, will cause the validation to display error and/or warning messages.
Note however, that relative fixed paths can only be validated with the structure in which they are defined. Be sure to use the GetValidationMessages() method on an instantiated relative fixed path to determine if the relative path used is valid at that time.
With the configuration in place, you can start generating code. Open the CompiledDomainModel Code Generator application in Sitecore (you need to have developer rights). It is located under the Sitecore menu > "Development Tools" > "CompiledDomainModel Code Generator"
Now you can see that the code generation has already been executed. Just use the "Copy to Clipboard" button and paste inside a .cs file in your solution (or use the "Download" button).
There are a few reasons why the generated code is targeted at just 1 source file:
The domain model that is generated uses the same inheritance structure as the Sitecore templates. If it is impossible to generate a valid C# domain model because of multiple inheritance, a detailed error message will be displayed. You can use contributing templates to deal with multiple inheritance.
As you may have noticed, there is a combo box that can be used to select a different "generator". This can be used to select a different code generation template.
If you place a .ascx file in the CustomGenerators folder, it will automatically show up in the combobox. I recommend using the same codebehind file (or inherit from it) as is used in DomainModelGenerator.aspx.
It is important to note that the standard validation functionality will only check the domain model if it is generated from the default generator. Also, creating your own version of the default generator is discouraged; you will not be able to benefit from future releases of the module. If there are features you would like to have added or bugs you would like to have fixed, please contact the author.
Two custom generators are added to the combobox by default. The SqlViewsGenerator can be used to generate a SQL script. The script adds or replaces a database schema to your SQL Server database (other databases should be easy to implement). The schema will contain views that correspond to the templates you have configured to be in the domain model. That way, you can easily find all items and their contents in the database.
So if you would have, for example, a template in Sitecore called "Product" that has the fields "Product number", "Price", "Amount in stock" and "Description". Then you would get a database view called DomainModelViews.Product with the following columns:
The views are useful if you need to investigate problems in your database or if you need to generate some reports without using the Sitecore API.
The second custom generator is called WcfServiceGenerator. It can be used to generate a WCF service. That way, you can have typed access to your Sitecore system from any service consumer.
Included in the source code is an example consumer application in the form of a Silverlight RSS feed reader. It is similar to the ASP.NET feedreader application.
Warning: the WcfServiceGenerator is purely experimental. In its current form, using it would create a huge security risk. This is because many of the operations use a SecurityDisabler. In short; any service consumer would have full control of the content in your Sitecore installation.
If you would like to use the WcfServiceGenerator in a production environment, then please contact me for advice. Also, if you have any suggestions, contact me.
After generating the domain model and pasting it into your solution, take a look at the code. You will see that it is documented. If the "Help" section of your templates and fields are filled, you will find the relevant information in the comments.
The following code examples are based on the demo application.
To start, you can get a typed wrapper for a Sitecore item by calling the static method ItemWrapper.CreateTypedWrapper(...). This will return a Domain Model object that best matches the item. If there is no match, you will still receive an Item wrapper (it will not contain typed access to the item's fields, but you can still use other functionality).
1: [Bindable(true)]
2: public Feed Feed
3: {
4: get
5: {
6: if (feed == null)
7: {
8: feed = ItemWrapper.CreateTypedWrapper(Sitecore.Context.Item) as Feed;
9: }
10: return feed;
11: }
12: set
13: {
14: feed = value;
15: }
16: }
In this example, the context item is passed to the static method to create a typed wrapper and the result is then cast to a Feed object (line 8). You could also use the constructor of the Feed class. That would throw an exception if you would pass it an item of the wrong type.
After you've created an item wrapper, you have access to the specific fields of the item. For instance, in an ASP.NET page/control.
1: <a href="<%# Feed.Url %>" target="_blank" name="feed_<%# Feed.Item.ID.ToString() %>">
2: <%# Feed.Name %>
3: </a>
As you can see, the Url and Name fields of the item can be referenced in a safe manner. If you prefer to use the Sitecore fieldrenderers, I would recommend using the following method.
1: <sc:Link runat="server" Item="<%# Feed.Item %>" Field="<%# MyDomainModel.Feed.FIELD_URL %>">
2: <sc:Text runat="server" Item="<%# Feed.Item %>" Field="<%# MyDomainModel.Feed.FIELD_NAME %>" />
3: </sc:Link>
This way, if the reference to the fieldname is incorrect, you will get an error when the ASP.NET page/control is compiled. This allows you to detect problems at an early stage. Otherwise, the renderers will just display nothing and it may not be clear that there actually is a problem.
In practice, the real advantage of this module is in logic in the domain model itself. It can be easily extended to support import/export functionality, business rules or anything else a domain model layer is useful for. Consider, for example, this way of extending the FeedItem domain model class with functionality to create a new item based on a .NET SyndicationItem.
1: namespace MyDomainModel
2: {
3: public partial class FeedItem
4: {
5: public FeedItem(Feed feed, SyndicationItem syndicationItem)
6: : this(feed.Item.Add(ItemUtil.ProposeValidItemName(GetTitle(syndicationItem)), new Sitecore.Data.TemplateID(FeedItem.TEMPLATE_ID)))
7: {
8: int maxSortOrder;
9: if (feed.Children != null)
10: {
11: maxSortOrder = feed.Children.Select(child => child.Item.Appearance.Sortorder).Max();
12: }
13: else
14: {
15: maxSortOrder = 0;
16: }
17: UpdateFromSyndicationItem(syndicationItem, maxSortOrder + 10);
18: }
19:
20: public void UpdateFromSyndicationItem(SyndicationItem syndicationItem)
21: {
22: UpdateFromSyndicationItem(syndicationItem, 0);
23: }
24:
25: public void UpdateFromSyndicationItem(SyndicationItem syndicationItem, int sortOrder)
26: {
27: using (new EditContext(Item, SecurityCheck.Disable))
28: {
29: Title = GetTitle(syndicationItem);
30:
31: if (syndicationItem.Links != null
32: && syndicationItem.Links.Count > 0
33: && syndicationItem.Links.First().Uri != null)
34: {
35: Link = syndicationItem.Links.First().Uri.ToString();
36: }
37:
38: if (syndicationItem.Summary != null)
39: {
40: Description = syndicationItem.Summary.Text;
41: }
42: else if (syndicationItem.Content != null && syndicationItem.Content is TextSyndicationContent)
43: {
44: Description = ((TextSyndicationContent) syndicationItem.Content).Text;
45: }
46:
47: if (sortOrder > 0)
48: {
49: Item.Appearance.Sortorder = sortOrder;
50: }
51: }
52: }
...
90:
91: }
92: }
As you can see, the constructor (line 5), takes 2 arguments: the feed under which the item can be placed, and a SyndicationItem to get the data from. The constructor immediately creates a new Sitecore Item and passes it to a generated constructor (line 6). After that, the item is used to fill the title, link and description fields (lines 29, 35, 40 and 44).
Also worth noting, is the easy way of navigating the domain model. If a template contains fields that link to other items, suitable accessors for these links are generated. Comments, for example, have a reference to the feed item they apply to. You can see this in the generated code:
1: /// <summary>
2: /// Description: The item in a feed that the comment aplies to
3: /// </summary>
4: public ItemWrapper FeedItem
5: {
6: get
7: {
8: return ItemWrapper.CreateTypedWrapper(GetField<InternalLinkField>(ID.Parse("{28E29796-50D2-4E51-88DE-A0089D2734BF}"), "FeedItem").TargetItem);
9: }
10: set
11: {
12: InternalLinkField field = GetField<InternalLinkField>(ID.Parse("{28E29796-50D2-4E51-88DE-A0089D2734BF}"), "FeedItem");
13: if (Object.Equals(field.Value, value))
14: {
15: return;
16: }
17: RaisePropertyChanging("FeedItem");
18: field.Value = value.Item.ID.ToString();
19: RaisePropertyChanged("FeedItem");
20: }
21: }
22:
23: public T GetFeedItem<T>() where T : ItemWrapper
24: {
25: return ItemWrapper.CreateTypedWrapper(GetField<InternalLinkField>(ID.Parse("{28E29796-50D2-4E51-88DE-A0089D2734BF}"), "FeedItem").TargetItem) as T;
26: }
You can use the FeedItem property to get the feed item that the comment applies to. If you know what type of item the field references, you can use the GetFeedItem<T>() method. This will return the referenced item if it can be cast to the passed in type (T). Similar methods are generated for fields with multiple values. Also, similar methods are available to access children, descendants, parent, ancestors and referrers (check the generated ItemWrapper class).
Another feature that can be used is the Fixed paths feature. Code is generated for all the fixed paths like so:
1: namespace MyDomainModel.FixedPaths.Content
2: {
3: /// <summary>
4: /// Access the item at /sitecore/content/Comments.
5: /// The availability of the item is validated in the databases: master.
6: /// </summary>
7: [FixedPathAttribute("{AFB27B25-41E5-4CFD-8C95-E40B9A95FA69}", "/sitecore/content/Comments", new string[] { "master" })]
8: public static partial class CommentsFixed
9: {
10:
11: public static ItemWrapper GetItemWrapper(Database database)
12: {
13: return new ItemWrapper(GetItem(database));
14: }
15:
16: public static ItemWrapper ItemWrapper
17: {
18: get
19: {
20: return GetItemWrapper(Sitecore.Context.Database);
21: }
22: }
23:
24: public static ItemWrapper ItemWrapperFromMaster
25: {
26: get
27: {
28: return GetItemWrapper(Database.GetDatabase("master"));
29: }
30: }
31:
32: private static Item GetItem(Database database)
33: {
34: Item item = database.GetItem(ID.Parse("{AFB27B25-41E5-4CFD-8C95-E40B9A95FA69}"));
35: if (item != null)
36: {
37: return "/sitecore/content/Comments".Equals(item.Paths.FullPath)
38: ? item
39: : (database.GetItem("/sitecore/content/Comments") ?? item);
40: }
41: else
42: {
43: return database.GetItem("/sitecore/content/Comments");
44: }
45: }
46: }
47: }
The generated code allows you to access the content item from code using this syntax: <DOMAIN_MODEL_NAMESPACE>.FixedPaths.<DOT_SEPARATED_PATH>.<ITEM_NAME>Fixed.ItemWrapper. This example shows how it is used to create a new comment for a FeedItem:
1: public void AddComment(string text)
2: {
3: using (new SecurityDisabler())
4: {
5: new Comment(FixedPaths.Content.CommentsFixed.ItemWrapper.Item, text, this);
6: }
7: }
Alternatively, if you need to access a lot of paths at (nearly) the same location, you could add an entry in the usings.
1: using MyDomainModel.FixedPaths.Content;
...
56: public void AddComment(string text)
57: {
58: using (new SecurityDisabler())
59: {
60: new Comment(CommentsFixed.ItemWrapper.Item, text, this);
61: }
62: }
If the item at the fixed path has a template that can be used to create a domain model object, then the generated code is slightly different.
1: namespace MyDomainModel.FixedPaths.Content
2: {
3: /// <summary>
4: /// Access the item at /sitecore/content/Feeds.
5: /// The availability of the item is validated in the databases: master.
6: /// </summary>
7: [FixedPathAttribute("{65163E61-BCA0-4746-817F-FDD475A1C320}", "/sitecore/content/Feeds", new string[] { "master" }, DomainObjectType = typeof(FeedCollection))]
8: public static partial class FeedsFixed
9: {
10:
11: public static FeedCollection GetFeedCollection(Database database)
12: {
13: return new FeedCollection(GetItem(database));
14: }
15:
16: public static FeedCollection FeedCollection
17: {
18: get
19: {
20: return GetFeedCollection(Sitecore.Context.Database);
21: }
22: }
23:
24: public static FeedCollection FeedCollectionFromMaster
25: {
26: get
27: {
28: return GetFeedCollection(Database.GetDatabase("master"));
29: }
30: }
31:
32: private static Item GetItem(Database database)
33: {
34: Item item = database.GetItem(ID.Parse("{65163E61-BCA0-4746-817F-FDD475A1C320}"));
35: if (item != null)
36: {
37: return "/sitecore/content/Feeds".Equals(item.Paths.FullPath)
38: ? item
39: : (database.GetItem("/sitecore/content/Feeds") ?? item);
40: }
41: else
42: {
43: return database.GetItem("/sitecore/content/Feeds");
44: }
45: }
46: }
47: }
This way, you can access the fixed path and get the correct type immediately.
Sometimes you will know the structure of part of the tree, but that structure can occur in different locations of the content tree. You will want to use relative fixed paths in this situation.
The following code is an example of how to hook up a relative fixed path that is always available on items with template 'HomePage' (this could be the setup for a multi-site solution). The code extends the generated partial class for the 'HomePage' template and should be placed in a separate file.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using MyDomainModel.RelativeFixedPaths.Roots.SiteConfig_05D48188D735420988119594A8FFA6A0;
6:
7: namespace MyDomainModel
8: {
9: public partial class HomePage
10: {
11: public SiteConfigFixed SiteConfig
12: {
13: get
14: {
15: // use syntax like: MyHomePage.SiteConfig.Settings.GlobalSetting.Setting.Text
16: return new SiteConfigFixed(Item);
17: }
18: }
19:
20: }
21: }
In both development and production environments, you will always need to ensure that the domain model is consistent with the templates in the database. That's why the validator is an essential tool. You can open it from the Sitecore menu > "Development Tools" > "CompiledDomainModel Validator".
If the model is inconsistent with the database, then a list of detailed error and warning messages is displayed.
The error messages must be addressed immediately, because they are issues that can compromise the behavior of your application. Warning messages are less important and usually do not damage the behavior, but they are still inconsistencies that should be resolved.
In addition to the validator application, the validation can also be executed on initialization of the application (enabled by default). Any error messages resulting from this will be written to the Sitecore log.
If you want to add custom validations, like checking for very specific runtime preconditions, you can extend the ValidateDomainModel pipeline (check the CompiledDomainModel.config file). Your errors and warnings will be displayed in the validator (and added to the log during Sitecore initialization).
Reports in the CompiledDomainModel are helpful pages that provide quick insight into the structure of the currently loaded domain model. They can also be useful for technical documentation. You can find links to reports at the bottom of the validation application within Sitecore.
The "Structure for loaded domain model" page displays a quick overview of the hierarchy of your domain model and the fields that reference fields in the Sitecore databases.
The "Fixed paths overview" displays what fixed paths have been included in the generated code (for each Sitecore database). The locations where domain objects are directly accessible (because they have matching templates) are marked with the domain object's class name.
A separate package is available for the FeedReader demo application. Make sure that the correct version of the CompiledDomainModel application is installed and then install the FeedReader demo application package. You can also get the source for this package, so that you can get a good idea of how to work with the CompiledDomainModel module. This simple example does not account for publishing, so please run it on a single database only (master).
The demo application is a simple RSS reader that can be used to download feeds into Sitecore. You can also add short comments to the items in the feeds.
Please not that it is a demo application; if you would like to use this functionality in production, you may need to address things like publishing, security, layout, style, etc.
Release 0.3.1.0 (initial release) containing the following features:
Release 0.4.0.0 containing the following features:
Release 0.5.0.0 containing the following features:
Release 0.5.5.0 containing the following features:
Release 1.0.0.0 containing the following features:
This module was created by Robin Hermanussen. For questions, remarks, contributions or anything else related to this module, you can contact him by E-mail: (mail is obfuscated for crawlers).