{"id":2068,"date":"2015-02-13T15:57:53","date_gmt":"2015-02-13T15:57:53","guid":{"rendered":"http:\/\/sitecore.robhabraken.nl\/?p=2068"},"modified":"2015-05-02T08:10:18","modified_gmt":"2015-05-02T08:10:18","slug":"setting-up-content-staging","status":"publish","type":"post","link":"https:\/\/www.robhabraken.nl\/index.php\/2068\/setting-up-content-staging\/","title":{"rendered":"Setting up content staging"},"content":{"rendered":"<p>Some time ago Sitecore offered the staging module, but since 6.3 it isn&#8217;t available anymore. Recently, one of our clients asked for such functionality and I thought we\u00a0could\u00a0easily\u00a0provide this by simply adding a publishing target. In general, this proved to be true and the solution works great, but there are some things you should know to create a fully independ\u00a0content staging environment, in\u00a0which the indexes play an important role.<\/p>\n<h3>Why content staging?<\/h3>\n<p>Having a preview function in Sitecore, some people wonder why you would need a separate staging environment. The answer is equally logical as surprising. Because Sitecore let&#8217;s you preview the page in the way it would be presented after a publish action, it respects the publish\u00a0restrictions\u00a0like date ranges and the non-publishable status of a version or an item. That&#8217;s nice, and by design, but we always teach content editors to make new content unpublishable until they\u00a0want to publish it, to prevent colleague editors from accidentially publishing their unfinished content.\u00a0Stalemate! You cannot preview your changes unless you&#8217;ll make your item publishable, but you do not want to yet, because it is still in preview..<\/p>\n<p>Using a separate content staging environment, we let editors publish their content to a separate website (where it is publishable), without any publish restrictions or the risk of publishing content publicly too early in the process.<\/p>\n<h2>The solution<\/h2>\n<p>We will create a separate content staging site for content editors in this blog post, by adding a publishing target and a separate set of indexes for this publishing target.<\/p>\n<h4>Demarcation<\/h4>\n<p>We will only set this up for the production environment, so the test and acceptance environment will not be changed. They keep their single publishing target. Furthermore, having a content management\u00a0server and multiple delivery servers, this\u00a0content\u00a0staging environment is only applicable for the content management server. So that&#8217;s our target environment: the content management server within the production environment needs an extra content staging website.<\/p>\n<h4>\u00a0What it takes<\/h4>\n<p>1) Duplicate your web database and rename it with a _Staging postfix\u00a0and\u00a0add a new connection string to your ConnectionStrings.config file accordingly:<\/p>\n<pre class=\"lang:default decode:true\">&lt;add name=\"staging\" connectionString=\"user id=sitecore;password=secret;Data Source=(local)\\SQLEXPRESS;Database=DemoSitecore_Staging\"\/&gt;<\/pre>\n<p>2) Add a new database definition to your databases node in the Web.config. The id of this node must be equal to the name of your connection string node:<\/p>\n<pre class=\"lang:default decode:true\">&lt;!-- Staging --&gt;\r\n&lt;database id=\"staging\" singleInstance=\"true\" type=\"Sitecore.Data.Database, Sitecore.Kernel\"&gt;\r\n  &lt;param desc=\"name\"&gt;$(id)&lt;\/param&gt;\r\n  &lt;icon&gt;Network\/16x16\/earth.png&lt;\/icon&gt;\r\n  &lt;securityEnabled&gt;true&lt;\/securityEnabled&gt;\r\n  &lt;dataProviders hint=\"list:AddDataProvider\"&gt;\r\n    &lt;dataProvider ref=\"dataProviders\/main\" param1=\"$(id)\"&gt;\r\n      &lt;disableGroup&gt;publishing&lt;\/disableGroup&gt;\r\n      &lt;prefetch hint=\"raw:AddPrefetch\"&gt;\r\n        &lt;sc.include file=\"\/App_Config\/Prefetch\/Common.config\" \/&gt;\r\n        &lt;sc.include file=\"\/App_Config\/Prefetch\/Webdb.config\" \/&gt;\r\n      &lt;\/prefetch&gt;\r\n    &lt;\/dataProvider&gt;\r\n  &lt;\/dataProviders&gt;\r\n  &lt;proxiesEnabled&gt;false&lt;\/proxiesEnabled&gt;\r\n  &lt;proxyDataProvider ref=\"proxyDataProviders\/main\" param1=\"$(id)\" \/&gt;\r\n  &lt;archives hint=\"raw:AddArchive\"&gt;\r\n    &lt;archive name=\"archive\" \/&gt;\r\n    &lt;archive name=\"recyclebin\" \/&gt;\r\n  &lt;\/archives&gt;\r\n  &lt;Engines.HistoryEngine.Storage&gt;\r\n    &lt;obj type=\"Sitecore.Data.$(database).$(database)HistoryStorage, Sitecore.Kernel\"&gt;\r\n      &lt;param connectionStringName=\"$(id)\" \/&gt;\r\n      &lt;EntryLifeTime&gt;30.00:00:00&lt;\/EntryLifeTime&gt;\r\n    &lt;\/obj&gt;\r\n  &lt;\/Engines.HistoryEngine.Storage&gt;\r\n  &lt;cacheSizes hint=\"setting\"&gt;\r\n    &lt;data&gt;20MB&lt;\/data&gt;\r\n    &lt;items&gt;125MB&lt;\/items&gt;\r\n    &lt;paths&gt;5MB&lt;\/paths&gt;\r\n    &lt;itempaths&gt;100MB&lt;\/itempaths&gt;\r\n    &lt;standardValues&gt;4MB&lt;\/standardValues&gt;\r\n  &lt;\/cacheSizes&gt;\r\n&lt;\/database&gt;<\/pre>\n<p>3) Add a\u00a0publishing target item in your &#8220;system\/Publishing targets&#8221; folder in the master database. You can point towards the actual database via the same id or name as mentioned in the previous steps:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2112\" src=\"http:\/\/sitecore.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publishing-target.png\" alt=\"publishing-target\" width=\"750\" height=\"350\" srcset=\"https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publishing-target.png 750w, https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publishing-target-300x140.png 300w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>4)\u00a0Add an alternative site node\u00a0to access the staging environment, be sure to change the database property. If you have more sub domains or websites you want to stage, you can include new entries for those sites as well:<\/p>\n<pre class=\"lang:default decode:true\">&lt;site name=\"website_demo_staging\" hostName=\"www.staging.demo.local\" targetHostName=\"www.staging.demo.local\" virtualFolder=\"\/\" physicalFolder=\"\/\" rootPath=\"\/sitecore\/content\" startItem=\"Demo\/Homepage\" database=\"staging\" domain=\"extranet\" allowDebug=\"true\" cacheHtml=\"false\" htmlCacheSize=\"10MB\" registryCacheSize=\"0\" viewStateCacheSize=\"0\" xslCacheSize=\"5MB\" filteredItemsCacheSize=\"2MB\" enablePreview=\"true\" enableWebEdit=\"true\" enableDebugger=\"true\" disableClientData=\"false\" language=\"nl-NL\" \/&gt;<\/pre>\n<blockquote><p>Of course, you can configure your HTML cache, HTML and data cache sizes the way you&#8217;re used to or like best; these\u00a0examples only show what&#8217;s needed for the creation of the staging environment and use the basic Sitecore configuration as a starting point.<\/p><\/blockquote>\n<p>So, this basically creates the new publishing target and adds it to your Sitecore client in the following places. A new database:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2115\" src=\"http:\/\/sitecore.robhabraken.nl\/wp-content\/uploads\/2015\/02\/new-database.png\" alt=\"new-database\" width=\"750\" height=\"350\" srcset=\"https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/new-database.png 750w, https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/new-database-300x140.png 300w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\" \/><\/p>\n<p>And a new publishing target in all publishing dialogs:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2116\" src=\"http:\/\/sitecore.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publish-item.png\" alt=\"publish-item\" width=\"614\" height=\"671\" srcset=\"https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publish-item.png 614w, https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publish-item-275x300.png 275w\" sizes=\"auto, (max-width: 614px) 100vw, 614px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>But\u00a0more importantly, when you click on &#8220;Change&#8221; in the Publish ribbon at the Restrictions chunk, you will see a list of checkboxes for each publishing target on the third tab:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2109\" src=\"http:\/\/sitecore.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publishing-settings.png\" alt=\"publishing-settings\" width=\"750\" height=\"350\" srcset=\"https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publishing-settings.png 750w, https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publishing-settings-300x140.png 300w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\" \/><\/p>\n<p>Initially, those checkboxes are unchecked, meaning that the item will be published to all publishing targets by default. By selecting this publishing target, you will prevent other editors from\u00a0accidentally publishing the item to the world wide web, without setting publish restrictions that prevent you from previewing the item.<\/p>\n<p>If you now publish an item, the message you&#8217;ll see asks if you want to publish to &#8220;every publishing target&#8221;, but since this publishing action respects the publish restrictions, this actually means &#8220;to all allowed\u00a0publishing targets&#8221;. Despite this message, the item will <strong>not<\/strong> be published to the Internet (web database):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2106\" src=\"http:\/\/sitecore.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publish-message.png\" alt=\"publish-message\" width=\"750\" height=\"350\" srcset=\"https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publish-message.png 750w, https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/02\/publish-message-300x140.png 300w\" sizes=\"auto, (max-width: 750px) 100vw, 750px\" \/><\/p>\n<p>You can now visit the staging website on your content delivery server and test your new content without any restrictions!<\/p>\n<h4>Indexability<\/h4>\n<p>But we have one more challenge, as I explained in the introduction of this article: indexes. Our site uses indexes for overviews of data sources (data types, like news or products), it uses indexes for populating dropdowns and for building up the FAQ section for example. So we need to separate them too. It might not be the case for your implementation, at least to this extent, but indexes sure are an integral part of your website and I think this solution comes in handy for almost all site implementations.<\/p>\n<p>5) The following step consists of adding a configuration file for the new staging index:<\/p>\n<pre class=\"lang:default decode:true\">&lt;configuration xmlns:patch=\"http:\/\/www.sitecore.net\/xmlconfig\/\"&gt;\r\n  &lt;sitecore&gt;\r\n    &lt;contentSearch&gt;\r\n      &lt;configuration type=\"Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch\"&gt;\r\n        &lt;indexes hint=\"list:AddIndex\"&gt;\r\n          &lt;index id=\"sitecore_staging_index\" type=\"Sitecore.ContentSearch.LuceneProvider.SwitchOnRebuildLuceneIndex, Sitecore.ContentSearch.LuceneProvider\"&gt;\r\n            &lt;param desc=\"name\"&gt;$(id)&lt;\/param&gt;\r\n            &lt;param desc=\"folder\"&gt;$(id)&lt;\/param&gt;\r\n            &lt;!-- This initializes index property store. Id has to be set to the index id --&gt;\r\n            &lt;param desc=\"propertyStore\" ref=\"contentSearch\/databasePropertyStore\" param1=\"$(id)\" \/&gt;\r\n            &lt;configuration ref=\"contentSearch\/indexConfigurations\/defaultLuceneIndexConfiguration\" \/&gt;\r\n            &lt;strategies hint=\"list:AddStrategy\"&gt;\r\n              &lt;!-- NOTE: order of these is controls the execution order --&gt;\r\n              &lt;strategy ref=\"contentSearch\/indexUpdateStrategies\/onPublishEndAsyncStaging\" \/&gt;\r\n            &lt;\/strategies&gt;\r\n            &lt;commitPolicyExecutor type=\"Sitecore.ContentSearch.CommitPolicyExecutor, Sitecore.ContentSearch\"&gt;\r\n              &lt;policies hint=\"list:AddCommitPolicy\"&gt;\r\n                &lt;policy type=\"Sitecore.ContentSearch.TimeIntervalCommitPolicy, Sitecore.ContentSearch\" \/&gt;\r\n              &lt;\/policies&gt;\r\n            &lt;\/commitPolicyExecutor&gt;\r\n            &lt;locations hint=\"list:AddCrawler\"&gt;\r\n              &lt;crawler type=\"Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch\"&gt;\r\n                &lt;Database&gt;staging&lt;\/Database&gt;\r\n                &lt;Root&gt;\/sitecore&lt;\/Root&gt;\r\n              &lt;\/crawler&gt;\r\n            &lt;\/locations&gt;\r\n          &lt;\/index&gt;\r\n        &lt;\/indexes&gt;\r\n      &lt;\/configuration&gt;\r\n    &lt;\/contentSearch&gt;\r\n  &lt;\/sitecore&gt;\r\n&lt;\/configuration&gt;<\/pre>\n<p>Let&#8217;s call this new file\u00a0<strong>Sitecore.ContentSearch.Lucene.Index.Staging.config<\/strong> to accompany the already existing Core, Master and Web configs for Lucene.<\/p>\n<p>6) &#8230; but as you can see, we&#8217;ve used a new publishing strategy in the above configuration, so this wouldn&#8217;t work without adding the following section to the\u00a0Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config as a child of the indexUpdateStrategies node:<\/p>\n<pre class=\"lang:default decode:true\">&lt;onPublishEndAsyncStaging type=\"Sitecore.ContentSearch.Maintenance.Strategies.OnPublishEndAsynchronousStrategy, Sitecore.ContentSearch\"&gt;\r\n  &lt;param desc=\"database\"&gt;staging&lt;\/param&gt;\r\n  &lt;!-- Whether or not a full index rebuild should be triggered when the number of items in the EventQueue exceeds the number specified \r\n        in Config.FullRebuildItemCountThreshold. --&gt;\r\n  &lt;CheckForThreshold&gt;true&lt;\/CheckForThreshold&gt;\r\n&lt;\/onPublishEndAsyncStaging&gt;<\/pre>\n<p>You have to define a\u00a0new\u00a0strategy for your staging database, because the\u00a0onPublishEndAsync strategy\u00a0is only watching the event queue from a single database, being the web database by default. So you have to copy that strategy definition and define your own strategy that points towards the new staging database. If you would have multiple publishing targets or databases besides the default web database, mind that you should add a strategy for each of them.<\/p>\n<p>7) Add a SearchContext helper class to retrieve the index that belongs to the current context programmatically. I quickly threw together a class and property\u00a0that return the currently applicable index, which comes in handy when you query the index a lot from your code:<\/p>\n<pre class=\"lang:c# decode:true  \">public static class SearchContext\r\n{\r\n    public static string GetSearchIndex\r\n    {\r\n        get\r\n        {\r\n            var index = \"sitecore_web_index\"; \/\/ default\r\n\r\n            if (Sitecore.Context.Database != null &amp;&amp; !string.IsNullOrEmpty(Sitecore.Context.Database.Name))\r\n            {\r\n                switch (Sitecore.Context.Database.Name.ToLowerInvariant())\r\n                {\r\n                    case \"master\":\r\n                        index = \"sitecore_master_index\";\r\n                        break;\r\n                    case \"staging\":\r\n                        index = \"sitecore_staging_index\";\r\n                        break;\r\n                }\r\n            }\r\n            return index;\r\n        }\r\n    }\r\n}<\/pre>\n<p>You can now easily get to the index that belongs to your current context by typing:<\/p>\n<pre class=\"lang:c# decode:true \">ContentSearchManager.GetIndex(SearchContext.GetSearchIndex);<\/pre>\n<h4>\u00a0Targeting the content management server<\/h4>\n<p>8) As mentioned before, this staging environment shouldn&#8217;t be available on your delivery servers, nor should it be active on the\u00a0test and acceptance environment (in our case). Since we are always using multiple specifically targeted builds in Visual Studio, we can\u00a0add this line to build configurations of environments that do not need\u00a0a staging version of the website:<\/p>\n<pre class=\"lang:default decode:true \">&lt;ExcludeFilesFromDeployment&gt;App_Config\\Include\\Sitecore.ContentSearch.Lucene.Index.Staging.config&lt;\/ExcludeFilesFromDeployment&gt;<\/pre>\n<p><strong>Tip:<\/strong> we do not use the &#8220;Debug&#8221; and &#8220;Release&#8221; builds that VS defaults to, but we have a build type for each target enviroment,\u00a0like &#8220;Development&#8221;, &#8220;Test&#8221;, &#8220;Acceptance&#8221;, &#8220;Production Management&#8221; for the content management server(s) and &#8220;Production Delivery&#8221; for all delivery servers.\u00a0This way you cannot only maintain multiple Web.config (and other config) translations to alter your config files per target server (like the different\u00a0URL&#8217;s in the sites section), but you can also keep all those deltas within your version control system of choice and you can also add specific project configurations for each environment.<\/p>\n<p>You can now\u00a0remove the new site node within the Web.config translations of the servers that do not need the staging environment:<\/p>\n<pre class=\"lang:default decode:true\">&lt;site name=\"website_demo_staging\" xdt:Transform=\"Remove\" xdt:Locator=\"Match(name)\"\/&gt;<\/pre>\n<p>This, of course, also applies to your connection string configuration.<\/p>\n<h2>Conclusion<\/h2>\n<p><em>As you can see, Sitecore is really flexible in setting up different content previewing strategies. A separate staging website, only available on your content management server, is easy to configure without having to setup a whole new server. This extensive blog post (it turned out a bit longer than I intended to..) should help you getting up and running pretty fast, but if you\u00a0run into anything that isn&#8217;t complete, or compatible with your process, I would like to hear from you and I&#8217;ll see if I can improve\u00a0my article.<\/em><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Some time ago Sitecore offered the staging module, but since 6.3 it isn&#8217;t available anymore. Recently, one of our clients asked for such functionality and I thought we\u00a0could\u00a0easily\u00a0provide this by simply adding a publishing target. In general, this proved to be true and the solution works great, but there are some things you should know [&hellip;]<\/p>\n","protected":false},"author":9,"featured_media":2151,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[39],"tags":[40,42,43,41,21,20],"class_list":["post-2068","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to","tag-content","tag-how-to","tag-indexes","tag-preview","tag-publishing","tag-sitecore"],"jetpack_featured_media_url":"https:\/\/www.robhabraken.nl\/wp-content\/uploads\/2015\/01\/scaffolding.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/posts\/2068","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/comments?post=2068"}],"version-history":[{"count":74,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/posts\/2068\/revisions"}],"predecessor-version":[{"id":2149,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/posts\/2068\/revisions\/2149"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/media\/2151"}],"wp:attachment":[{"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/media?parent=2068"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/categories?post=2068"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.robhabraken.nl\/index.php\/wp-json\/wp\/v2\/tags?post=2068"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}