Blog posts by Steve Celius2015-05-25T23:44:02.8470000Z/blogs/Steve-Celius/Optimizely WorldSimple color picker property/blogs/Steve-Celius/Dates/2015/5/simple-color-picker-property/2015-05-25T23:44:02.8470000Z<p>Lets say you wanted a simple block to show a title, just to let your editors break up a long content area with some contextual spacing. Simple stuff. However, you want to let the editor decide the background color, and that means the editor need to be able to change the text color too, or you might end up with black text on a black background. A couple of text fields will handle that.</p>
<p>Looking good:</p>
<p><img src="/link/8a698016c5074110a22f68ec58689074.aspx" alt="Image Title Block Preview.png" /></p>
<p>And the editorial experience?</p>
<p><img src="/link/4f60cb8c48b6468faa8b5c39e244d6d9.aspx" alt="Image Title Block All Properties - Bad.png" /></p>
<p>Come on - knowing CSS color codes by hand is not that hard, is it? What if it could look like this:</p>
<p><img src="/link/c90779fc7f4442bcb15840895b2640aa.aspx" alt="Image Title Block All Properties.png" /></p>
<p>With the power of Dojo, this is amazingly simple:</p>
<pre class="language-csharp"><code>[ContentType(
DisplayName = "Title",
Description = "Title with styling options",
GroupName="Content")]
[SiteImageUrl(thumbnail: EditorThumbnail.Content)]
public class TitleBlock : SiteBlockData
{
[Display(
GroupName = SystemTabNames.Content,
Order = 10)]
[CultureSpecific]
public virtual string Title{ get; set; }
[Display(
GroupName = SystemTabNames.Content,
Name = "Text Color",
Order = 50)]
[ClientEditor(ClientEditingClass = "dijit/ColorPalette")]
public virtual string TextColor { get; set; }
[Display(
GroupName = SystemTabNames.Content,
Name = "Background Color",
Order = 60)]
[ClientEditor(ClientEditingClass = "dijit/ColorPalette")]
public virtual string TextBackgroundColor { get; set; }
}</code></pre>
<p>The magic is to specify the client editing class as "dijit/ColorPalette". That's it. This particular widget is a built-in one, and we can use it without having to do anything else.</p>
<p>For good measure, I hid a couple of more advanced properties on the Settings tab:</p>
<p><img src="/link/bc820068d8264f07aafdf0a34d15130e.aspx" alt="Image Title Block All Properties - Settings.png" /></p>
<p>At least I'm giving some advice by using the description for the property.</p>
<p><strong>Disclaimer!</strong> I haven't found a way to limit or specify what colors the palette should show, and depending on your design, this is like giving editors access to Comic Sans. Use responsively.</p>Google Analytics Tracking and Customization/blogs/Steve-Celius/Dates/2014/12/google-analytics-tracking-and-customization/2014-12-10T11:44:00.0000000Z<p>The new version of the Google Analytics Add-on for EPiServer has some exiting new features that allows you better control over how and what you track with Google Analytics. Also, with the new possibility to install Add-ons through Visual Studio, the install is quick and easy to do, and makes it easy to get started customizing the tracking script.</p>
<h2>Installing the Google Analytics Add-on as a Nuget package</h2>
<p>Install Google Analytics Add-on as a Nuget package from the EPiServer Nuget feed</p>
<pre class="language-csharp"><code>install-package EPiServer.GoogleAnalytics</code></pre>
<p>Configure it to connect to your Google Analytics account from Admin Mode:</p>
<p><img src="/link/62e895ce456b47f4991911f63029d4ba.aspx?id=114174" alt="Image configure-ga-admin.png" /></p>
<p>Select the tracking method that your Google Analytics account supports. Note! Most of the code examples here require Universal Analytics (UA):<br /><img src="/link/3d6c0e657150466186aaa1f325f341ea.aspx?id=114175" alt="Image configure-ga-admin-select-script-option.png" /></p>
<p>If you want to track Ecommerce events, make sure your UA account has Ecommerce tracking turned on in the Google Analytics administration view. If you want to track Enhanced Ecommerce events, also enable “Enhanced Ecommerce Reporting”:</p>
<p><img src="/link/0dd114c8f9e74580a42c941e54caf445.aspx?id=114176" alt="Image configure-ga-ecommerce-setup.png" /></p>
<p><strong>Note!</strong> Do not mix Ecommerce and Enhanced Ecommerce tracking on your pages.</p>
<p>Check that the Add-on is installed and working by looking at the source of any of your pages:</p>
<p><img src="/link/93602e527f7e464588aa34db861bc165.aspx?id=114181" alt="Image ga-see-page-script.png" /></p>
<p>You can also install the Google Analytics Debugger extension for Chrome:<br /><a href="https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna">https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna</a></p>
<p><a href="https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna"><br /></a>It will show debug information in the Chrome console:<br /><img src="/link/f1332a2c88074f5aa6fcfc96bf6064f7.aspx?id=114177" alt="Image ga-debugging-tools.png" /></p>
<p>More information about troubleshooting Google Analytics: <a href="https://developers.google.com/analytics/resources/articles/gaTrackingTroubleshooting">https://developers.google.com/analytics/resources/articles/gaTrackingTroubleshooting</a></p>
<p><strong>Note!</strong> If you plan to customize the tracking, you need to install the add-on through Visual Studio. It will add the necessary reference to the Add-on assembly. You should not add a reference to an assembly installed as an add-on through the Add-on UI as it would break your site if the add-on is uninstalled. </p>
<p>If you have worked with previous versions of the add-on, you’ll now see that the new version has fewer dependencies, which makes for a cleaner install.</p>
<h2>Support for Universal Analytics</h2>
<p>Google has launched a major update to the Google Analytics offering called Universal Analytics. It is a better tracking script, extensible, easier to use, and most importantly, allows more types of tracking, especially when it comes to Ecommerce.</p>
<p>I recommend reading up on the new possibilities, there are numerous blog posts that shows how and why and can really inspire your imagination. The official documentation is also good reading:<br /><a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/">https://developers.google.com/analytics/devguides/collection/analyticsjs/</a> </p>
<p>Unless you have good reasons not too, you should upgrade your site to use Universal Tracking, and use the new script. If you haven’t customized how you track using the previous version of the Add-on, you can install the new one, and change the tracking type:<br /><img src="/link/3d6c0e657150466186aaa1f325f341ea.aspx?id=114175" alt="Image configure-ga-admin-select-script-option.png" /></p>
<p>Make sure you also upgrade your current analytics account in the administration interface for Google Analytics.</p>
<h2>Ecommerce Tracking</h2>
<p>If the basic page view tracking is not enough, you typically want to extend with more tracking instructions. For an example, if you’re running an EPiServer Commerce site, the new Enhanced Ecommerce tracking that is part of Universal Analytics is a must.</p>
<p>The “old” tracking only allowed tracking purchases, and would show you what products were most popular among people actually buying them. The new Enhanced Ecommerce allow you to track both impressions, detail views, product clicks, adding to the cart, and the whole checkout process, giving you an easy way to see where people fall off.</p>
<p><img src="/link/b27a9f12af954e27950fec899695e7a5.aspx?id=114180" alt="Image ga-report-shopping.png" /></p>
<p>With all this information about product views, lists, clicks etc. you’ll get a whole new view of how your site is actually performing.</p>
<p><strong>Note!</strong> The Add-on will help you add this tracking, but it wont do it for you. As every site is different, it would be hard to add this tracking automatically. Don’t worry, I’ll show you how.</p>
<h2>Tracking Basics</h2>
<p>The <a href="http://webhelp.episerver.com/14-1/EN/Default.htm#Addons/GoogleAnalytics/Developers_GoogleAnalytics.htm%3FTocPath%3DAdd-ons%7CGoogle%2520Analytics%2520for%2520EPiServer%7CConfiguring%2520Google%2520Analytics%2520for%2520EPiServer%7C_____1">online documentation for the Google Analytics Add-on</a> explains how the add-on works pretty well, so I will not repeat that here. I recommend you browse through that documentation if you have trouble following what I do in the code below. </p>
<p>If you want to add more script lines to the tracking script before the pageview is sent off to Google, you need to work with AnalyticsInteraction classes. This is done through extensions to HttpContext and HttpContextBase (for MVC).</p>
<p>Web Forms:</p>
<pre class="language-csharp"><code>Page.Context.AddAnalyticsEvent("button", "click", "nav buttons", 4, clearWhenContextChanged: true);</code></pre>
<p>MVC Controller:</p>
<pre class="language-csharp"><code>ControllerContext.HttpContext.AddAnalyticsEvent("button", "click", "nav buttons", 4, clearWhenContextChanged: true);</code></pre>
<p>The heavy lifting is done in one of the classes inheriting from GASyntax, like the UniversalSyntax class which is responsible for retrieving all registered AnalyticsInteraction objects for a request and render them correctly.</p>
<p>This is also the class you need to change if you want to take full control over the script rendering.</p>
<h2>Creating Your Own Syntax Implementation</h2>
<p>The<a href="https://github.com/OXXAS/CommerceStarterKit"> Commerce Starter Kit</a> has quite a lot of customized tracking, and has it’s own syntax class. We register it with the service locator like this:</p>
<pre class="language-csharp"><code>[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class InitializeAnalytics : IConfigurableModule
{
public void Initialize(InitializationEngine context)
{
}
public void Preload(string[] parameters) { }
public void ConfigureContainer(ServiceConfigurationContext context)
{
// Our own syntax implementation
context.Container.Configure(c => c.For<UniversalSyntax>().Use<UniversalSyntaxEx>());
}
public void Uninitialize(InitializationEngine context)
{
}
}
</code></pre>
<p>Our enhanced UniversalSyntaxEx looks like this: </p>
<pre class="language-csharp"><code>public class UniversalSyntaxEx : UniversalSyntax
{
// standard script
protected string _gaScript = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\r\n(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\r\nm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\r\n})(window,document,'script','//www.google-analytics.com/analytics.js','ga');";
public override string BuildTrackingScript(ScriptBuilderContext appenderContext, SiteTrackerSettings siteSettings,
out bool requiresScriptReference)
{
requiresScriptReference = false;
if (siteSettings == null)
{
return null;
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("/* Begin GA Script */");
stringBuilder.AppendLine(_gaScript);
stringBuilder.AppendLine(string.Format("ga('create', '{0}', 'auto');", siteSettings.TrackingId));
stringBuilder.AppendLine("// Extended Tracking");
stringBuilder.AppendLine(AppendExtendedTracking(siteSettings, ref requiresScriptReference));
stringBuilder.AppendLine("// Plugin Script");
stringBuilder.AppendLine(this.GetPluginScript());
if (siteSettings.TrackAuthors && !string.IsNullOrEmpty(appenderContext.Author))
{
stringBuilder.AppendLine("// Custom Author Tracking");
stringBuilder.AppendLine(this.GetCustomDimension("Author", CustomVariables.AuthorVariable, appenderContext.Author));
}
ContentReference contentReference = new ContentReference(appenderContext.PageId);
ICollection<AnalyticsInteraction> interactions = EPiServer.GoogleAnalytics.Helpers.Extensions.GetInteractions(appenderContext.InteractionStore);
// This is where the interesting stuff happens
// All custom interactions are added here
stringBuilder.AppendLine("// Begin Interactions");
foreach (AnalyticsInteraction interaction in interactions)
{
// Skip any interactions that are tied to a specific page
if (ContentReference.IsNullOrEmpty(interaction.ContentLink) == false &&
contentReference.Equals(interaction.ContentLink) == false)
{
continue;
}
stringBuilder.AppendLine(interaction.GetUAScript());
}
stringBuilder.AppendLine("// End Interactions");
// Clear any interactions that should not persist
// across a request
this.ClearRedundantInteractions(interactions);
stringBuilder.AppendLine("ga('send', 'pageview');");
stringBuilder.AppendLine("/* End GA Script */");
return stringBuilder.ToString();
}
protected string AppendExtendedTracking(SiteTrackerSettings siteSettings, ref bool requiresScriptReference)
{
Dictionary<string, object> extendedTracking = this.GetExtendedTracking(siteSettings);
requiresScriptReference = extendedTracking.Count > 0;
return this.SerializeTrackerSettings(extendedTracking, siteSettings.TrackingScriptOption.ToString());
}
private string GetCustomDimension(string dimensionName, string dimensionIndex, string value)
{
return string.Format("ga('set', '{0}{1}', '{2}');", dimensionName, dimensionIndex, value);
}
}
</code></pre>
<p>This class will render the script, any plugin, the interactions and then send it off to Google.</p>
<p>Personally, I find that adding AnalyticsInteraction objects using the standard exension methods is a bit tedious. I’d rather have full control over the script instead of using the dictionary, so I created my own AnalyticsInteraction variation:</p>
<pre class="language-html"><code>public class UniversalAnalyticsInteraction : AnalyticsInteraction
{
// We're not using these, but we need the InteractionKey to be unique
// Note! We always mark this for deletion after is has been rendered
// to the page.
public UniversalAnalyticsInteraction() :
base("overridden-interaction", true, Guid.NewGuid().ToString())
{
}
public UniversalAnalyticsInteraction(string script)
: base("overridden-interaction", true, Guid.NewGuid().ToString())
{
Script = script;
}
/// <summary>
/// The javascript to add to the tracking script
/// </summary>
/// <remarks>
/// The javascript must be complete and valid, or it might break
/// the whole analytics tracking feature.
/// </remarks>
public string Script { get; set; }
public override string GetUAScript()
{
return Script;
}
}
</code></pre>
<p>If you know how the script should look like, you can use this one, and just set the script directly. You can add it like this:</p>
<pre class="language-csharp"><code>protected void AddInteraction(HttpContextBase context, string script)
{
var interactions = EPiServer.GoogleAnalytics.Helpers.Extensions.GetInteractions(context);
UniversalAnalyticsInteraction interaction = new UniversalAnalyticsInteraction(script);
interactions.Add(interaction);
}
</code></pre>
<p>If we look at the example from the Google Analytics documentation for <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#product-detail-view">measuring a product detail view</a>, it could look like this (from a product detail MVC controller): </p>
<pre class="language-csharp"><code>HttpContextBase context = ControllerContext.HttpContext;
// The plugin is required for tracking enhanced ecommerce
AddInteraction(context, "ga('require', 'ec');"));
// This would come from your commerce product
AddInteraction(context, "ga('ec:addProduct', {'id': 'P12345', 'name': 'Android Warhol T-Shirt', 'category': 'Apparel', 'brand': 'Google', 'variant': 'black'});"));
// This ties the product to the detail action
AddInteraction(context, "ga('ec:setAction', 'detail');");
</code></pre>
<p>Note! Since the add-on will end the script with a "send pageview" method, you do not need to include that yourself.</p>
<p>If you plan to use Enhanced Ecommerce actions on most pages on your site, moving the ga('require', 'ec'); script to your own IPluginScript class is a good idea. Add this to your IInitializableModule:</p>
<pre class="language-csharp"><code>// Add enhanced ecommerce to all pages
context.Container.Configure(c => c.For<IPluginScript>().Use<RequireEnhancedCommercePlugin>());
</code></pre>
<p>This is how the RequireEnhancedCommercePlugin look like in the Commerce Starter Kit:</p>
<pre class="language-csharp"><code>public class RequireEnhancedCommercePlugin : IPluginScript
{
public string GetScript()
{
ICurrentMarket currentMarket = ServiceLocator.Current.GetInstance<ICurrentMarket>();
IMarket market = currentMarket.GetCurrentMarket();
string script = "ga('require', 'ec');\n";
script = script + string.Format("ga('set', '&cu', '{0}');", market.DefaultCurrency);
return script;
}
}
</code></pre>
<p>It adds a require statement for the ec plugin. Since the starter kit supports multiple markets it also registers the current currency so any prices or conversions will be tracked correctly in Google Analytics.</p>
<h2>Enhanced Ecommerce Helper Library</h2>
<p>Getting all the script valid can be a bit cumbersome, there are <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce#ecommerce-data">different field objects</a> for different actions, and some fields are mandatory, others not. To help you out I've create a small helper project to make this easier. You can find it on Github: <a href="https://github.com/EPiServerNorway/UniversalTrackingHelper">https://github.com/EPiServerNorway/UniversalTrackingHelper</a></p>
<p>Adding a require statement for the ec plugin would be done like this with the helper library:</p>
<pre class="language-csharp"><code>using EPiCode.GoogleAnalyticsTracking;
...
Tracking tracking = new Tracking();
// Get the script
string script = tracking.Require("ec");
// Add it as an interaction (as shown earlier)
AddInteraction(context, script);
</code></pre>
<p>See the <a href="https://github.com/OXXAS/CommerceStarterKit/blob/master/src/web/Business/Analytics/GoogleAnalyticsTracking.cs">GoogleAnalyticsTracking class</a> in the Commerce Starter Kit for more examples on how to use the helper library.</p>
<h2>Session State storage for AnalyticsInteractions</h2>
<p>The analytics interactions are by default stored in Session state, and you can - in theory - add an interaction that spans page requests. I would advise against it as is hard to predict the order of the interactions.</p>
<h2>IPluginScript supports only one registration</h2>
<p>It is important to be aware that only on IPluginScript should be registered at any one time. Only one will be used, and if you have several registered classes with a ServiceConfiguration attribute, which one is selected cannot be predicted. Make sure you register your own through an IInitializableModule.</p>
<h2>Implementation of Google Analytics Tracking in the Commerce Starter Kit</h2>
<p>The <a href="https://github.com/OXXAS/CommerceStarterKit">Commerce Starter Kit</a> uses Enhanced Ecommerce tracking, and the current implementation contains:</p>
<ul>
<li>Tracking regular page views</li>
<li>Tracking the product detail view</li>
<li>Tracking product impressions from the Configurable Wine Search List</li>
<li>Reviewing the cart (with products)</li>
<li>Checkout page (with products)</li>
<li>Payment page (with products)</li>
<li>Receipt page with purchase command tracking order total and more</li>
</ul>
<p>Still remaining:</p>
<ul>
<li>Add to cart command (in lists and details view)</li>
<li>Related products (impressions) on product detail page</li>
<li>Returns (from Commerce Manager)</li>
<li>Category view (list pages)</li>
<li>Click product link</li>
<li>Add payment method as a checkout option</li>
</ul>
<h2>Interesting Analytics Data</h2>
<p>When you start tracking ecommerce, you'll quickly see that you're getting a lot of useful data to base your decisions on. Here are some simple examples:</p>
<h3>Checkout Funnel</h3>
<p>See Checkout funnel for device categories to find out if your checkout works across devices<br /><img src="/link/b27a9f12af954e27950fec899695e7a5.aspx?id=114180" alt="Image ga-report-shopping.png" /></p>
<p>Example: if smartphones are not able to purchase due to an error or incompatible payment provider you should see a high abandonment rate on the Checkout Behaviour: </p>
<p><img src="/link/d74e4951a4404db4b95980f0b82c7ad2.aspx?id=114178" alt="Image ga-report-abandonment.png" /></p>
<h3>Product List Performance</h3>
<p>The start page is important, do you get click-through on all products on the start page, or should you change some of them for something else?</p>
<p>Are people just browsing and not adding anything to the cart? Compare timespans with and without campaigns with discounts, are your customers motivated by price. Is your Cart-to-Detail rate low? How good is the content on your detail pages? Are people seeing what they need in order convert?</p>
<p><img src="/link/45984d64108e4b728c9e31bb567e9228.aspx?id=114179" alt="Image ga-report-product-performance.png" /></p>
<p>Remember, statistics can be misleading, this also applies to your tracking, and if you've got a bug in your tracking code you could be looking at some really strange results. Question your data.</p>
<p>When you have started tracking, you should also think about defining goals and actions in order to meet your goals, and then see how this all ties together. Are your customers behaving the way you think they are on your site? Maybe you need to change how you work, present product information, run campaigns, discount products etc.</p>
<p><span>And then see conversion rate increase. All with the new Google Analytics add-on for EPiServer. Happy tracking!</span></p>From the Trenches: Slow MainBody Property/blogs/Steve-Celius/Dates/2011/1/From-the-Trenches-Slow-MainBody-Property/2011-01-30T22:51:13.0000000Z<p><strong>Symptoms</strong></p> <p>On a recent support case I was involved in, a customer had several sites (6) running on a 32 bit Windows Server 2008 in an enterprise setup.</p> <p>Many of the sites, but not all, was slow at times, and had high cpu utilization. </p> <p>All the sites had recently been migrated from EPiServer CMS 4, and only been live for approx a week.</p> <p><strong>Diagnostics</strong></p> <p>This is a typical indication of one or more slow templates, with non-optimized code of some sort. However, one or two slow templates typically would not slow the whole server down, so some sort of trigger is needed.</p> <p>A quick way of finding slow running templates is to have a look a the current requests on the sites. IIS 7 will happily show what is going on at any given moment, and can quickly show what requests take the most time.</p> <p>Open IIS, make sure the server is selected:</p> <p><a href="/link/abc9f0db3b3f4ef8b204ff556e478163.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="iis-wp" border="0" alt="iis-wp" src="/link/33e17fdf8bef4d43b4566302bd713d43.png" width="403" height="285" /></a> </p> <p>Click “Worker Process” and you’re presented with a list of all the running sites (or the worker process associated to it to be specific).</p> <p><a href="/link/42250e757dc34f3e9407ccc78c40cbb8.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="iis-wp-detail" border="0" alt="iis-wp-detail" src="/link/600baedc6f234371a4fd038b08dc77cc.png" width="586" height="177" /></a> </p> <p>As you can see, IIS will show the CPU of the processes and the memory usage. Hit F5 to refresh the list to see which sites maintain a high cpu usage.</p> <p>Double click one of the sites, and it will show the current requests:</p> <p><a href="/link/7789c959efa94673a5aed68d2867df8e.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="iis-requests" border="0" alt="iis-requests" src="/link/557e00cc96af4a81a9d7a3be2b94a371.png" width="600" height="215" /></a></p> <p><em>(click image for full size)</em> </p> <p>You can see all urls and more importantly, how long they have been running. In the example above, we can see that the request has been running for more than 40 seconds. </p> <p>Not good. At all.</p> <p>One such template will not break your site, unless it is hit often. If the request is waiting for a database search, you can have quite many of these before you’ll see any problems, like filling the request queue.</p> <p>In our case, we could see several requests taking a long time, and most of them used the same template. We had a suspect.</p> <p>It turned out, there were lots of pages using the same template, and many of them performed quite bad, also in test and development environments. You know you’re almost home when you can reproduce the problem in your development environment.</p> <p>After some analysis, we found out that the template was showing pages with rather large amounts of content, up to 700KB of content actually. Problem was, the template was really simple, and had no problems in EPiServer CMS 4, so something has changed.</p> <p>The content was mostly bad html copied from Microsoft Word, with a large markup to text ratio. My first impulse was that all the markup was choking the Friendly Url Rewriter filter handler which parses and looks for urls in the rendered html. Turning it off (by selecting the Null Url Rewrite Provider in episerver.config) did not help though, so it had to be something else.</p> <p>Next up is to reduce the logic gradually until you’ve got the least amount of logic that still reproduce the problem. In our case, we ended up with an aspx (a template) that only rendered the MainBody property using the EPiServer Property control. The page took 30 seconds to render when the cache was cleared, but the second request only took milliseconds.</p> <p>Almost there, it had to be something EPiServer CMS 6 do when we ask for the property. Skipping the Property control (using CurrentPage[“MainBody”]) had no effect, so it is inside the EPiServer property data or in the data layer fetching the content.</p> <p>Good performance on the second request would indicate the data layer was at fault, and the real problem was hid by caching. But no. </p> <p>I added logging in the different stages of the page life cycle, using System.Diagnostics.Debug.WriteLine in init, load, prerender, render and unload. DebugView (dbgview.exe on <a title="http://live.sysinternals.com/" href="http://live.sysinternals.com/">http://live.sysinternals.com/</a>) from System Internals shows the logging (just remember to start it as an Administrator). The timing showed that the problem was actually when I read the MainBody from the PropertyDataCollection on CurrentPage.</p> <p>The property was of type XHtmlLongString, and contained lots and lots of markup. Have you spotted the problem yet?</p> <p>Dynamic Content.</p> <p>The first time the MainBody is served (not read from the database), it is parsed to check for Dynamic Content, and this took a long time for these pages because of the large amount of markup that needed parsing (and it was bad markup too.)</p> <p><strong>The Cure</strong></p> <p>Changing from XHtmlLongString to just ordinary LongString property on the page type helped a lot. These pages did not have Dynamic Content, and never will, so it is an acceptable compromise.</p> <p>I mentioned the need for a trigger in the beginning of this article, which some of you might already have figured out. The sites had just recently been migrated from CMS 4, so all urls are new, and the search engines had just discovered this, indexing lots of pages with lots of data. The XHtmlLongString property uses lots of cpu during parsing, so that is the cause of the high cpu utilization.</p> <p>Additionally, these huge pages uses lots of memory when cached, so the cache was trimmed quite often – adding insult to injury. This is a 32 bit server, with 6 sites on it and only 4GB of memory, which in this particular case should be enough, but won’t over-perform under stress.</p> <p>The patient is now doing fine.</p>Deployment in EPiServer CMS – Slides from Partner Summit/blogs/Steve-Celius/Dates/2010/6/Deployment-in-EPiServer-CMS--Slides-from-Partner-Summit/2010-06-08T15:33:40.0000000Z<p><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="iStock_000009519157Small" border="0" alt="iStock_000009519157Small" align="right" src="/link/2b1c6c8f23cb46b8a8a6ab4dc768d173.jpg" width="233" height="240" /> </p> <p>At the EPiServer Partner Summit 2010 in Malmö I did a presentation about deployment in EPiServer CMS.</p> <p>If you did not attend it, you missed topics like:</p> <ul> <li>Planning </li> <li>External Dependencies </li> <li>Installation </li> <li>Server Options </li> <li>IIS configuration differences </li> <li>Shared VPPs </li> <li>Cache Invalidation </li> <li>Continuous Integration </li> <li>Virtualization </li> <li>Security </li> <li>and more… </li> </ul> <p>Luckily for you – it is all <a href="/link/80c03252511449889964370b788aaf53.zip" target="_self">downloadable here</a>, right now. Enjoy!</p>log4net Tips: Do not declare a logger in Global.asax.cs/blogs/Steve-Celius/Dates/2010/4/log4net-Tips-Do-not-declare-a-logger-in-Globalasaxcs/2010-04-26T17:22:34.0000000Z<p>If you declare your own logger in Global.asax.cs (for the Global class) you will effectively turn off all log4net logging for the entire site.</p> <p>Like this:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">using</span> log4net;<br /><span style="color: #0000ff">using</span> System.Reflection;<br />...<br /><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> Global : EPiServer.Global<br />{<br /> <span style="color: #008000">// Very bad idea</span><br /> <span style="color: #0000ff">static</span> <span style="color: #0000ff">readonly</span> ILog _log = log4net.LogManager.GetLogger(<br /> MethodBase.GetCurrentMethod().DeclaringType);<br /> ...<br />}<br /></pre>
<br /></div>
<p>Seems like not everyone knows about this – so consider yourself warned!</p>log4net tips: Shortening the type name/blogs/Steve-Celius/Dates/2010/3/log4net-tips-Shortening-the-type-name/2010-03-31T12:24:26.0000000Z<p>If you’re using an appender layout like this:</p> <div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"> <div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"> <pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum1"> 1:</span> <span style="color: #0000ff"><</span><span style="color: #800000">layout</span> <span style="color: #ff0000">type</span><span style="color: #0000ff">="log4net.Layout.PatternLayout"</span><span style="color: #0000ff">></span></pre>
<!--CRLF-->
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum2"> 2:</span> <span style="color: #0000ff"><</span><span style="color: #800000">conversionPattern</span> <span style="color: #ff0000">value</span><span style="color: #0000ff">="%date %level [%thread] %type.%method - %message%n"</span> <span style="color: #0000ff">/></span></pre>
<!--CRLF-->
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"><span style="color: #606060" id="lnum3"> 3:</span> <span style="color: #0000ff"></</span><span style="color: #800000">layout</span><span style="color: #0000ff">></span></pre>
<!--CRLF--><!--CRLF--></div>
</div>
<p>The conversion pattern is:</p>
<div id="codeSnippetWrapper">
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet">%date %level [%thread] %type.%method - %message%n</pre>
<font size="2" face="Courier New"></font></div>
<p>Which is the default one for the fileLogAppender shipping with EPiServer (note that type & method logging is slow, but immensely useful when you need it.)</p>
<p>A log line could look like this:</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper">
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet">2009-12-09 17:27:04,655 INFO [6] Microsoft.Samples.Runtime.Remoting.Channels.Pipe.PipeConnection.Write - 18.3.1 Scheduler info: 2780<span style="color: #0000ff">></span> Write string Content-Type</pre>
<br /></div>
<p>The whole type name is included. In most cases, you just need the name of the class and method, which will save you some logging space (more on that later), but it will also make your log easier to read.</p>
<p>The %type pattern supports this syntax: %type{n} where <n> is the number of class/namespaces to include (from the right). </p>
<p>Changing the pattern to: </p>
<div id="codeSnippetWrapper">
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet">%date %level [%thread] %type{1}.%method - %message%n</pre>
<font size="2" face="Cordia New">
<br /></font>yields the following log:</div>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper">
<pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet">2009-12-09 17:27:04,655 INFO [6] PipeConnection.Write - 18.3.1 Scheduler info: 2780<span style="color: #0000ff">></span> Write string Content-Type</pre>
<br /></div>
<p>Read more about this in the <a href="http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html">log4net SDK documentation on the PatternLayout</a> class.</p>Long Running SQL Queries and Timeoutshttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/2009/9/Long-Running-SQL-Queries-and-Timeouts/2009-09-01T21:30:46.0000000ZI’m doing some work on a site with a huge database and needed to delete a couple of properties from a page type, which repeatedly gave me SQL timeout exceptions. In EPiServer CMS 4 the timeout could be tweaked with the EPnQueryTimeout value under...FindPagesWithCriteria and Performancehttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/2009/8/FindPagesWithCriteria-and-Performance/2009-08-23T22:21:46.0000000ZLately I have heard from several developers that FindPagesWithCriteria is evil, and should be avoided at all costs. I’ve heard it from different people, in different unrelated cases, and some comments are needed. Yes, FindPagesWithCriteria can slo...Edit Panel plug-in performancehttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/2009/8/Edit-Panel-plug-in-performance/2009-08-21T01:48:08.0000000ZI’ve been doing some performance tuning on a large site recently, and one of the problems we had was that clicking pages in edit mode was quite slow. Especially when compared to just clicking around on the site in view mode. When you click a page ...Breaking Change in R2 Might Affect Securityhttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/10/Breaking-Change-in-R2-Might-Affect-Security/2008-10-15T21:12:10.0000000ZIn EPiServer CMS 5 R2 there is a change in the GetPage() functionality which is marked as a breaking change. The change concerns security, specifically how security is checked when you call DataFactory. GetPage(). Up to now, which means all...Installer Issues in R2http://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/10/Installer-Issues-in-R2/2008-10-14T02:00:54.0000000ZThe new installer shows great promise, but as usual - new things need new adjustments. First; a big hands up to the brave souls in the EPiServer development team diving into the deep end and making a new installer. It is not just an installer, it...Strange Error Message In CMS 5http://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/10/Strange-Error-Message-In-CMS-5/2008-10-13T20:19:36.0000000ZI was deploying a new CMS 5 R1 (SP? ) site today, to a server without EPiServer Manager, brand new . NET Framework 3.5 installation and generally nothing set up as it should. Having copied, configured, tuned and tweaked security and settings the...EPiServer System Files Have Movedhttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/10/EPiServer-System-Files-Have-Moved/2008-10-11T21:33:39.0000000ZIf you’ve read Fredrik's recent blog postings, or checked the folder of a newly installed R2 site, you might have noticed that the list of folders has shrunk. “Private” files, or system files, belonging to EPiServer has been moved out of the web...Client Caching in R2 and Localhosthttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/10/Client-Caching-in-R2-and-Localhost/2008-10-11T16:56:26.0000000ZI was investigating some other issue in R2 and was looking at the output in Fiddler. Much to my surprise, all the requests had caching set to private, and even though resources are cached (they will return a 304 not modified header) it still...A little gotcha with the Multiplexing Membership Providerhttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/9/A-little-gotcha-with-the-Multiplexing-Membership-Provider/2008-09-08T18:25:02.0000000ZThe MultiplexingMembershipProvider will call GetUser before it calls ValidateUser when authenticating a user. If you set the default provider to be your own provider, ValidateUser will be called first. So, a little different behaviour there, no...Speed up Visual Studiohttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/6/Speed-up-Visual-Studio/2008-06-25T01:07:05.0000000ZSome tips and tricks I've picked up along the road. They might not give you a huge performance improvement, but the golden rule to disable or remove things you do not need, is always for the better. Do your own experiments and see if it helps. These...The EPiCode Cache Frameworkhttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/6/The-EPiCode-Cache-Framework/2008-06-16T01:40:00.0000000ZOn the EPiServer Developer Summit I demonstrated a way to speed up your lists by caching the content after the first initial loading, using a small framework that helps you extract the code that loads the content. It handles the caching completely...Join us on IRChttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/6/Join-us-on-IRC/2008-06-13T13:09:00.0000000ZFor some time now (over a year actually), we have had an #epicode channel on irc. freenode. org. We initially meant it to be a place to discuss the modules on EPiCode, but it has turned out to be a great place to discuss anything related to...Developer Summit Presentationhttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/6/Developer-Summit-Presentation/2008-06-03T15:08:00.0000000ZFor all of you that attended my presentation at the EPiServer Developer Summit on Friday, thank you so much for attending! We filled the biggest room! Hope you liked what you saw, and learned something new. If you have any questions regarding the...Using the Gaia ajax framework in EPiServerhttp://labs.episerver.com/en/Blogs/Steve-Celius/Dates/112266/4/Using-the-Gaia-ajax-framework-in-EPiServer/2008-04-11T17:14:00.0000000ZIf you're working with Gaia Ajax Widgets in your EPiServer project, you need to read this blog post. In short, it fixes a problem where the Gaia callbacks are done in the context of your start page instead of the currently loaded page. The funny...