Blog posts by Deane Barker2023-11-30T02:41:22.0000000Z/blogs/deane-barker/Optimizely WorldStop Managing Humans in Your CMS/blogs/deane-barker/dates/2023/11/stop-managing-humans-in-your-cms/2023-11-30T02:41:22.0000000Z<p>Too many times, a content management system becomes a people management system. Meaning, an organization uses the CMS to manage all the information about the people that consume the content -- users, members, customers, etc.</p>
<p>I think this happened because it was a natural extension of a CMS's user management system, and it integrated easily with permissions. The problem was (is?) that in doing this, we naturally bound identity to channel. If our user database was in the same system that we managed our website, then we effectively cemented those two together. But remember, a <em>user</em> is not necessarily a <em>customer</em>.</p>
<p>It's 2023, and we need to "lift out" the notion of human identity from our channel management. We need to acknowledge that humans exist apart from channels -- they might consume things on our website, but that's just one touch point. If we bind their identity to it, we miss out on chances to leverage personalization in other channels.</p>
<p>In 2021, we acquired Z<span><span class="ui-provider a b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak">aius</span></span>, which became the <a href="https://www.optimizely.com/enhancements/data-platform/">Optimizely Data Platform</a>. Z<span><span class="ui-provider a b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak">aius</span></span> is a customer data platform (CDP), which is now part of <a href="https://www.optimizely.com/company/press/optimizely-one/">Optimizely One</a>.</p>
<p>CDPs are a "master identity databases" where you can track all your customer's behaviors and aggregate information about them. You can then query this information and use the resulting "segments" to take actions or run other campaigns. (Some years back, right when the CDP market began to expand, I wrote <a href="https://deanebarker.net/tech/blog/cdp/">a long-ish primer on what it was and why it mattered</a>. It might be worth a refresher.)</p>
<p>Using ODP, we can manage identity and preference data in a system apart from our website. In practical terms, this means we're moving "profile" data out of our CMS and into a centralized system. Our CMS will still have the concept of an "account" which is used for practical concerns like permissions, but all the preference data moves to ODP.</p>
<p>ODP has a fantastic API, and just to prove how easy it is to connect this to CMS, I wrote a quick C# wrapper for it. <a href="https://github.com/deanebarker/opti-data-profiles/">Here's a GitHub repo.</a></p>
<p>(You don't care about code? That's fine, but, read on -- I'll pick up the narrative in a bit.)</p>
<p>Now, I don't want to call this an "add-on" or even a "project," because it's just so simple. There's just one class with no dependencies. Use it as-written, or use it as a reference -- it's not hard to understand. You're welcome to do whatever you want with this, just know that I wrote it in about an hour, and it's not an official release. It's mainly to demonstrate how easy ODP is to use.</p>
<p>This class allows you to push and pull data to/from an ODP customer object bound to the current CMS user. So our CMS will store the barebones information for them to manage their authentication, and ODP will store everything else. This class helps you connect the two ("I know who they are in CMS, so get me the same person from ODP...")</p>
<p>It's this simple to get their identity and store some data about it.</p>
<pre>var profile = OptiDataProfile.GetCurrentProfile();
profile.SetValue("preferred_mode", "dark");
</pre>
<p>That value will be pushed into an ODP object (which will be created if it doesn't exist) which can be bound to a key from their user account (usually email, but you could use user ID too). So, next time they come to your CMS site, just get the profile and read out what you want.</p>
<pre>var profile = OptiDataProfile.GetCurrentProfile();
var mode = profile.GetString("preferred_mode");
</pre>
<p>(And keep in mind that you could always push or pull this from JavaScript. If you want to do that, go nuts. This code only helps if you want to talk directly from CMS to ODP.)</p>
<p>Now, back to our larger story --</p>
<p>You might be saying, "Why would I store their preferred viewing mode in ODP? Doesn't this only mean something to the CMS...?"</p>
<p><em>This is the mode of thinking we need to get out of.</em></p>
<p>There are at least two practical benefits to storing this in ODP, and this is where we get a little philosophical --</p>
<p><strong>First, using ODP centralizes your user data and extends it to every channel. </strong>It transcends any particular digital property, and there's value in that. In this case, maybe they want Dark Mode on other properties? Should you change the mode for them on your mobile app (which has access to the same profile)? How about your emails? </p>
<p>The larger, deeper point is this: <em>you should extend your user data past the immediate channel</em>. To store this the channel-managing system (CMS, in this case), is effectively to say, "I will never use this data outside this website," and I feel like that's a little short-sighted. Think bigger, aim higher.</p>
<p><strong>Second, ODP is a customer database that you can query. </strong>If you store their preferred viewing mode in a data platform like this, you can use it when creating customer segments. Now you have a location where you can take their viewing mode into account alongside all your other customer data.</p>
<p>They prefer Dark Mode and they're over 50? Maybe they're sensitive to light. Sell them some sunglasses.</p>
<p>They prefer Dark Mode, their first name is "Willow", and they live in Colorado? Maybe they're ecologically sensitive. Offer to round up their purchase and donate it to Greenpeace.</p>
<p>They prefer Dark Mode and they bought black lipstick? Maybe they're emo or goth. Promote the new <em>My Chemical Romance</em> album. (I didn't read <a href="https://www.wikihow.com/Be-Emo">this article</a>, I swear...)</p>
<p>Anyway, you get the idea.</p>
<p>Maybe we're going off the rails a bit on Dark Mode, but these two points are indisputable:</p>
<ol>
<li>Having more data about a customer gives you more options to segment them for personalization, experimentation, or other campaigns. Swap out dark mode for anything else: address, phone number, employer, favorite pizza topping, whatever.<br /><br /></li>
<li>Your customer's identity as a human with preferences and information trail should transcend the channel they're using at any given moment. They're not just your "web site visitor." They're a <em>human</em>. Treat them with that perspective.</li>
</ol>
<p>More customer data is better than less. And having it in one location provides other benefits, so long as it's effortless to set it, get it, change it, and put it back.</p>
<p>Hopefully this library helps with that, and this article gave you a new perspective on it.</p>Introducing Liquid Templating/blogs/deane-barker/dates/2023/1/introducing-liquid-templating/2023-01-19T17:50:41.0000000Z<div class="preview-wrapper">
<div class="wrapper">
<div class="article-wrapper">
<div class="article-body">
<p>We’re offering an optional new way to template Optimizely CMS sites. It's been in open beta for a while, but we're ready for its "official" unveiling.</p>
<h2>TLDR</h2>
<p>You can now template a CMS project in Liquid, instead of Razor.</p>
<p>Liquid is a common templating language. It doesn't allow raw C#, and the templates can be detached from the Visual Studio project and be stored elsewhere -- on the file system, in the cloud, as managed content, in...Dropbox? Wherever.</p>
<p>Additionally, some Liquid extensions make it possible for template developers to query the repository directly, rather than depending on C# developers to write retrieval logic into backend code. This is a significant change from the standard MVC conventions.</p>
<p>This should allow teams to more easily distribute their CMS development, and it doesn't take much imagination to extrapolate how this might change how you develop CMS projects. In addition to the direct changes of a new language, the new option will have multiple indirect changes to project organization, staffing, DevOps, project planning, and even…philosophy?</p>
<p>...but first, let’s back up a bit.</p>
<h2>Some History</h2>
<p>If there’s been one perennial thing about developing on a Episerver/Optimizely CMS project, it's that the work revolves around Visual Studio. Perhaps it’s more accurate to say that it revolves around a C# application -- Visual Studio is just the usual window into that.</p>
<p>Now, to be clear, I love C#. I think it’s a great language. We've pushed our CMS all the way out on .NET Core 7 now, which uses <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11">C# 11</a>, and there are some amazing features there.</p>
<p>However, C# isn’t for everyone. And one of great limitations of being so…pure, is that it can be hard to distribute the work on a CMS project. If you have front-end developers that work in HTML, CSS, and JavaScript, templating becomes an unnecessary roadblock. Back when I was a partner, I often had to “translate and coerce” HTML from a front-end developer into Razor templates.</p>
<p>Razor, of course, is the default templating language for ASP.NET. But it’s not even really a “language,” as much as it’s just transpiled C# -- . But not only does it transpile to C#, you can simply write raw C# inside a Razor file.</p>
<p>(In fact, I saw an implementation the other day where there was only one <em>actual </em>controller, and most of the controller-ish logic was in a C# block inside the Razor layout page. On first glance, it horrified me, but the customer made a decent case for it, and it worked for them, so… yay, flexibility?)</p>
<p>And Razor is just...not spectacular as a templating language? I mean, it works fine, and it was undoubtedly better than Webforms, but the syntax is not entirely pleasant. (Yes, Tag Helpers make it better. We're implementing those.) Razor is basically C# mixed with HTML, and while this is effective, I've never heard anyone say that they love it.</p>
<p>(Yes, I'm hedging a little here. I know a lot of C# devs probably like Razor, and that's awesome -- you can keep using it. But among non-C# devs, especially front-end devs, the feelings are likely quite different.)</p>
<h2>What We're Trying to Improve</h2>
<p>We saw three problems we might be able to solve:</p>
<ol>
<li>To template a CMS project in Razor, you usually need to work within the Visual Studio project</li>
<li>Razor allows unrestricted computational access to the process</li>
<li>Razor is maybe, possibly, kinda not the greatest templating language in the world</li>
</ol>
<p>So, what’s the solution?</p>
<p>Clearly, one solution is to shift to client-based templating, via React or something similar. We’ve had a headless API since 2015, and we’re releasing a GraphQL API at the end of Q1. Additionally, every URL into our CMS is technically a headless API URL, since <a href="https://docs.developers.optimizely.com/content-cloud/v1.5.0-content-delivery-api/docs/using-friendly-urls">you can get JSON back by simply changing one request header</a>.</p>
<p>Many of our customers have done this – implemented their templates in React or Vue or whatever. And this is awesome -- we're so excited that customers are using our product this way.</p>
<p>But many more customers aren’t ready for this and might never be. Client-based templating requires a skillset that some customers just don’t have and don’t want to hire for. Additionally, there’s a vast new layer of development and DevOps complexity that comes with it.</p>
<h2>Introducing Liquid Templating</h2>
<p>With this in mind, we’ve released a new server-side templating option: Fluid, which is an implementation of Liquid.</p>
<p><a href="https://shopify.github.io/liquid/">Liquid</a> is a templating language invented by Shopify a decade ago. It’s since become something of a standard – there are implementations in many different languages. The C# implementation is called <a href="https://github.com/sebastienros/fluid">Fluid</a>, and was written by <a href="https://github.com/sebastienros">Sebastien Ros</a>, who works at Microsoft on the ASP.NET core team.</p>
<p>(It can sometimes get confusing. For <em>most </em>intents and purposes, "Fluid" and "Liquid" are the same thing. But technically, <em>Liquid </em>is the syntax, and <em>Fluid </em>is the implementation of that syntax in C#. The terms will be used interchangeably.)</p>
<p>ASP.NET is designed to encapsulate output generation in what are called “View Engines.” The framework can have multiple view engines registered, and can check in with them, sequentially, to find one that will generate output. Usually, there’s just the one registered by default – Razor.</p>
<p>Our Fluid implementation has been written as a View Engine, which can be swapped in place of Razor (or loaded <em>in addition</em> to Razor, but more on that later).</p>
<p>Enabling Liquid is a process of including the code, then adding a couple lines of configuration to your Startup file.</p>
<p>Here’s what Liquid allows you to do, superficially, which corresponds to the three issues from above that we're trying to solve.</p>
<ol>
<li>Store templates in a different repository, using a different DevOps process</li>
<li>Write templates in a safe, sandboxed language that doesn’t allow “root” computational access</li>
<li>Use a more pleasant templating language than Razor</li>
</ol>
<p>There are undeniable advantages here. This will allow you to distribute workload among different types of developers, and limit full project re-deploys – a templating change should not require you to redeploy and restart the entire instance. Templates can be stored in some other repository and deployed through some other process.</p>
<h2>Downstream Changes</h2>
<p>Now, you might think…"Interesting, but this is a minor change...."</p>
<p>But it’s bigger than you think. It has the potential to change the nature of a CMS project, both in the initial development and ongoing maintenance. And those differences might change how you develop.</p>
<p>With Liquid, it becomes easier to encapsulate different template retrieval logic</p>
<ul>
<li>Template retrieval is very flexible and writing a custom template provider is quite simple -- you just have to implement IFileProvider (here's <a href="https://gist.github.com/deanebarker/c5a1546e247c3886798f05729540a4a2">an example</a>). For example, I wrote one in about 15 minutes that retrieved templates from my personal Dropbox account.</li>
<li>In fact, there’s no reason templates can’t just be stored as managed content. A file is just an array of bytes, and that can be attached to a media object as a BLOB. It’s quite easy to enable your developers to change templates and “deploy” them by just dragging them into the UI.</li>
<li>To go even further, templates are just...strings. You can easily manage them as properties on a content object. Using the new “Headless Content” gadget in CMS12, a folder tree of templates could be created, and a “TemplateBlock” content type could expose a “TemplateCode” long string property. Create a new UIHint for an online editor, and now you can actually edit your templates directly in the UI.</li>
</ul>
<p>We could actually combine all these methods --</p>
<p>It’s quite simple to create a “MultiSourceTemplateProvider,” which checks multiple locations for a template. For example, it might check these three locations in order, then return the first template it finds.</p>
<ul>
<li>A template block as a content object</li>
<li>An attached .liquid file on a media asset</li>
<li>A file on the file system</li>
</ul>
<p>Using this logic, you can decide at what “level” you want to provide a template at.</p>
<p>(Here -- I did it: <a href="https://app.welcomesoftware.com/cloud/taskv3/(Here's%20some%20code%20that%20does%20all%20of%20this.)">MultiSourceTemplateProvider</a>)</p>
<h2>Mixing Razor and Liquid</h2>
<p>And there’s even another place we can go – you can actually<em> stack View Engines</em>. ASP.NET will query multiple engines, using the first one to return a view.</p>
<p>This means that “underneath” Liquid, you can have traditional Razor views. So your C# developers could provide default templating in Razor, which could be overridden by more dynamic view provided by a front-end developer in Liquid.</p>
<p>The inverse could work as well. By checking the Razor view engine first, your C# developers could “lock” templating by providing a Razor view for it, and only allowing the engine to “fallback” to Liquid when one isn’t provided.</p>
<p>And if you have a Razor project you want to convert to Liquid, you don’t need to do it all at once. Razor and Liquid can co-exist quite happily. In a long-running project, you could switch template languages as you implement new features.</p>
<h2>The Potential for Architecture Changes</h2>
<p>Clearly, template flexibility is going to increase. This means that the way you build websites might change.</p>
<p>Consider the process of mapping a template -- matching up a template with the requested content object. There's a convention here: you name the template for the content type. The relationship is usually always 1-to-1.</p>
<p>Along with alternative template retrieval, Liquid allows you to granularly control template mapping. A view name is a string, and within your own template provider, you can be very specific about what template you want returned and from where.</p>
<ul>
<li>If a block can be dynamically bound to other templates, in addition to a default template, would this remove the number of one-off BlockTypes required to support unique situations? Many one-offs are just to change output, because we tend to think of templating as a one-to-one-relationship to types. Liquid will make it easier to break out of that paradigm.</li>
<li>If templates can be uploaded to or edited from the UI, would this free your users to create their own page or block types from the UI, and supply their own templates? Creating types from the UI isn’t new, but with Liquid, the “last mile” of template specification and ingestion is now so much easier. Could you enable front-end developers to work in the UI and templates after a C# developer has bootstrapped and deployed the project?</li>
<li>Will the community coalesce around a template naming convention, to provide for dynamic template selection? A lightweight convention exists now – name your Razor files for the content type – but could this be extended to encompass template naming to specific languages, content branches, or even specific content? How about a template called “ButtonBlock-237.liquid” for content object #237? How about a template called “Sidebar-he.liquid” for Hebrew language formatting changes? How about a template called “Layout-CampaignSite.liquid” for a content under a particular site node?</li>
</ul>
<p>And what about per-object template specification from the UI:</p>
<ul>
<li>A block could have a restricted-access “Templating” tab, behind which is a code editor that would allow specific blocks to “bring their own template” for one-offs?</li>
<li>Or a content reference property that can link to a TemplateBlock object, from which the template can be retrieved, meaning blocks and pages could specify a template, or link to one?</li>
</ul>
<p>Additionally, we took a calculated risk when developing the Fluid integration to increase the power of template developers. Traditionally, the MVC convention has a backend developer do all logical data retrieval in the controller and provide that information to a view in the form of a model.</p>
<p>However, with Razor, that convention is a bit academic anyway, since you can write unrestricted C#. So we decided to allow and even encourage repository retrieval from the template by providing a set of repository functions. For example:</p>
<pre>{% assign children = ContentLoader.GetChildren(page.ContentLink) %}</pre>
<p>There are methods for retrieving:</p>
<ul>
<li>Specific content</li>
<li>The start page</li>
<li>Top-level pages</li>
<li>Children</li>
<li>Siblings</li>
<li>Parent</li>
<li>Ancestors</li>
<li>The nearest ancestor of a certain type</li>
<li>The nearest ancestor with a value in a specific property</li>
</ul>
<p>Template developers can now traverse and query the content repository themselves. And expanding this list with your own repository functions is quite easy.</p>
<p>(Don't worry: if this stresses you out, it's easy to disable.)</p>
<p>Our goal that was simple sites without much need for backend integration should be "developable" from the template and UI level. Could we reduce the need for deployments by...90%? We're not sure, but by empowering the front-end developers and detaching templating from the larger project, we think we're taking a step in that direction.</p>
<h2>Liquid/Fluid Documentation</h2>
<p>Over the last few months, I've written about 20,000 words of documentation on the Liquid/Fluid project itself:</p>
<p><a href="https://deanebarker.net/tech/fluid/">Dive Into Fluid</a></p>
<p>(The title is explained at the bottom of that page.)</p>
<p>To be clear, that documentation is about Liquid itself, not so much using it in Optimizely CMS (which is why it's on my personal site). But a lot of times, lack of familiarity with the base language is problematic, so I wanted to solve that.</p>
<p>I worked on this documentation with Sebastian, and hopefully it will show you how wildly flexible Fluid is. You can extend the language in so many ways:</p>
<ul>
<li><a href="https://deanebarker.net/tech/fluid/custom-tags-blocks/">Writing your own tag constructs</a></li>
<li><a href="https://deanebarker.net/tech/fluid/custom-filters/">Writing your own filters</a></li>
<li><a href="https://deanebarker.net/tech/fluid/conditionals/">Writing your own operators</a></li>
</ul>
<p>Using Fluid, you can develop an incredibly customized and tailored language for your front-end developers.</p>
<h2>Conclusion</h2>
<p>The larger point here is that Liquid brings with it a lot of flexibility in how you develop and manage templates. And rather than this being a simple language change, it has the potential to upend a lot of development paradigms that have evolved around our platform.</p>
<p>What we’re talking about is “editorial self-determination,” which is something I’ve been informally researching for years. To what level should we empower our users/editors to make structural changes to a website, beyond just content? What do we do about that gray area of highly-technical users that are more than editors, but not quite full-blown developers? Can we give them more tools to use? Can we offload some lightweight development tasks to them, freeing up our more experienced backend developers for other things?</p>
<p>And while this certainly isn't "low code," it might be... lowER code? It's a new layer of development for non-C# devs, in an effort to integrate our system into more diverse development teams and processes.</p>
<p>In the end, we're not sure how this will play out over the long-term, but we're interested to find out.</p>
<p>We've decided to simply open-source our Liquid templating, to encourage the development community to adopt and customize it. You can find it here:</p>
<p><a href="https://github.com/episerver/liquid-templating-cms">https://github.com/episerver/liquid-templating-cms</a></p>
<p>We'd love your feedback and your changes. Pull requests are welcome.</p>
<img src="https://pixel.welcomesoftware.com/px.gif?key=YXJ0aWNsZT00YzYwZGE5ZThkM2ExMWVkYTdiMWNhNWU0MDdmMDVlZQ==" width="1" alt="" height="1" /></div>
</div>
</div>
</div>The EPiServer Channel Wrapper/blogs/deane-barker/dates/2014/11/the-episerver-channel-wrapper/2014-11-13T03:08:24.0000000Z<p>We’re releasing a very early version of a wrapper we’ve written around EPiServer’s content channels. It reduces the amount of code necessary to connect to a content channel and send data to it.</p> <p>Additionally, it provides a framework for the persistent mapping of external content IDs (from whatever datasource) against EPiServer PageGUIDs. This makes it possible to know when you need to create new content, and when you need to update existing content (and then have the correct GUID to do that).</p> <p>The use case here is when you have content outside your EPiServer install that you need to sync against pages inside EPiServer. We see this so often in content integration scenarios that we finally built this wrapper to make it easier.</p> <p>(And yes, we know the <a href="/link/57bf8191787a4e1ba0a3245952065000.aspx">Service API</a> for CMS will be coming at some point. But that’s just the transmission mechanism. If even we swap the services out, there’s still a bunch of surrounding logic to our library that’s still necessary.)</p> <p>I wrote a blog post about the pattern here, which is worth reading as it discusses the reasoning and some of the architectural concepts behind the pattern (which, for the record, is not unique to EPiServer).</p> <p><a href="http://gadgetopia.com/post/9205">The “Import and Update” Pattern</a></p> <p>And the code is on GitHub here:</p> <p><a href="https://github.com/blendinteractive/episerverchannelwrapper">EPiServer Channel Wrapper</a></p> <p>There are example projects showing imports from code, XML, a SQL database, and another EPiServer installation (which makes it effectively a low-level form of content mirroring that you can modify, which is handy).  The README in the root has code samples and some architectural notes.</p> <p>There’s quite a bit left to do on it yet:</p> <ul> <li>Improve the deletion detection code </li> <li>Implement content hashing to determine if content has changed since the last run and pre-empt the web service call entirely </li> <li>Allow for parent page GUIDs, so as to allow nesting of content </li> </ul> <p> <br />Future improvements will come.  Pull requests are welcome.</p>EPiServer License Data Exporter/blogs/deane-barker/dates/2014/5/EPiServer-License-Data-Exporter/2014-05-09T23:42:01.0000000Z<p>EPiServer recently released <a href="https://serviceapi.episerver.com/">an API for their license data</a>.  The idea is that you can export license information from EPiServer and bring it down to your own infrastructure to query and otherwise manipulate it.</p> <p>I had been asking for something like it for years, starting with this blog post: "<a href="http://gadgetopia.com/post/8026">Why Your App Needs Automated Data Export</a>." In that post, I complained (nicely) about both EPiServer and <a href="http://www.insightly.com/">Insightly</a>, a CRM my company uses. </p> <p>After that post, Insightly created their API, and it was awesome in its simplicity – it just gave you a basic bulk export, from which you could do anything.  The idea is that your local tools are better at querying and data manipulation than a web-based system, so if I can just get this data down to my local machine, I’m better off.</p> <p>EPiServer was finally able to take a run at this system, and I worked with the internal team on it. I explained what I was looking for, and they responded with exactly what I asked for: simple, API-driven, bulk export.  It’s not complicated and it works beautifully.</p> <p>I adapted a library that I wrote for Insightly to download the data and populate SQL database.  It’s now on Github:</p> <p><a href="https://github.com/deanebarker/episerver-license-exporter">EPiServer License Data Exporter</a></p> <p>Provide an API key and a connection string, and this command line utility will populate a local SQL Server database with all your license information, enabling you to develop reporting around that information.  It’s extensible as well – all the objects are run through “mapping objects” with properties that use XPath to extract data.  It should be awfully easy for any .Net developer to figure out.</p> <p>I hope you get some value out of it, and I welcome pull requests if you have changes.</p>The Editorial Cycle in CMS 7/blogs/deane-barker/dates/2013/12/The-Editorial-Cycle-in-CMS-7/2013-12-23T00:37:21.0000000Z<p>Edit Mode fundamentally changed in CMS 7. Gone was the more traditional form-based editing, and we instead moved to an primarily on-page editing environment. This is a richer environment, certainly, but it’s also more complex and fragmented. One of the side effects of this move was that the mental model of traditional form submission got lost.</p> <p>In the CMS 6 (and earlier) Edit Mode environment, editors watched a form load with their content, they edited in that form, then saved that form. This is how the web has worked for decades, so the mental model was simple – there was a very clear transition to opening the editing environment, then making and saving your change. There was a mental line that users crossed “into” the editing environment, then back out of it.</p> <p>The blurring of this line in CMS 7 has caused confusion for some of our clients. It’s less clear now what constitutes a just a small change over a full version, and how this relates to things like:</p> <ul> <li>The “undo” and “redo” buttons in TinyMCE. </li> <li>The “undo” and “redo” links in the Autosave notification. </li> <li>The “Revert to Published” option </li> <li>The “Reject Changes” option </li> <li>And about a dozen other edge cases… </li> </ul> <p>CThis article is an attempt to dive deep into the editorial cycle – the editing, versioning, and publishing processes from when you create content or make an edit to it, all the way through to when that content is published to the world. This deep dive will be in the context of editing a page of content then following that edit through all the status permutations that follow.</p> <h2>Definitions</h2> <p>First of all, let’s make a few definitions for the purposes of this article –</p> <p><strong>Edit Mode</strong> is the mode in which you have the ability to change content. In CMS6 Edit Mode was very clear because it looked so different.  But in CMS7, the only different are a few icons on the outer edges of the page and some outlines around content.  To get into Edit Mode, you usually click the EPiServer QuickNavigator button in the upper right of the page.</p> <p>An <strong>Editing Session</strong> (note: this is a name I just made up to explain the concept; it’s not standard EPiServer nomenclature)<strong> </strong>is the period of time a specific page is in your browser while in Edit Mode and you are making changes to properties. The Editing Session starts when you make your first change and ends when you leave that page, or when it reloads for some reason (this will be important later). Note that publishing content or other actions will reload the page in the browser, effectively ending the Editing Session at the same time.</p> <p>Editing Sessions do not necessarily correspond to versions.  If you make one set of confined changes then publish, then one Editing Session results in one new version.  However, you might open a dozen Editing Sessions to work on a piece of content over the course of several days, then publish.  In this case, all of these Editing Sessions are in service of one new version of that content.</p> <p>The <strong>Published Version</strong> of the content is the version of content currently displayed to the public. (Helpfully, it’s labeled “Published Version” in the version list.)  This is only one Published Version of a piece of content at any one time.</p> <p>A <strong>Draft Version</strong> is a version of content which is not published yet. Either the content has never been published, or it’s a copy of the published version that you are currently editing. (In CMS6 and earlier, the status of the Draft Version was “Not Ready.”)</p> <p>A <strong>Property Edit</strong> (another name I just made up) is the act of editing a single property and having that edit saved. In the Edit Mode interface, you click on a property, make a change (either inline, or in whatever editor presents itself), then click out of the property on or the “Done” button, so that you trigger the “Saving…” indicator at the top of the window, and ultimately get the “Autosaved” dropdown interface (it is, in fact a dropdown – if you click the “Undo” link, you get another interface, which we’ll cover below).</p> <p>A full Property Edit looks like this.</p> <p><a href="/link/a3d5ccedca0c49d3a0d9dbaa36adfa7c.gif"><img title="movie" style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; border-left: black 1px solid; display: inline" border="1" alt="movie" src="/link/6fe07b705a764e33b12bba8c4149c04e.gif" width="442" height="142" /></a></p> <p>Notice that the editor (1) clicks into the property to activate the editor, (2) makes a change, (3) clicks out of the editor, and then (2) the autosave notice appears at the top of the page. That series of actions constitutes a Property Edit, and it can cause a lot of stuff to happen behind-the-scenes.</p> <p>Putting it all together, the simplest complete editing cycle for a piece of content looks something like this:</p> <ol> <li>View a page in View Mode </li> <li>Enter Edit Mode </li> <li>Make a Property Edit, which… </li> <li>…creates a new Draft Version </li> <li>Optionally make more Property Edits (on the new Draft Version) in multiple Editing Sessions </li> <li>Publish the content, which… </li> <li>…changes the status of both the Draft Version (it becomes “Published”)… </li> <li>…and the currently Published Version (it becomes “Previously Published”). </li> </ol> <p>Now, there are a lot of options and edge cases that weave in around that simple scenario, so read on…</p> <h2>The Editing Session</h2> <p>Just visiting a page in Edit Mode does not create a new version. You can browse the entire website in Edit Mode without changing anything. However, so long as you are in Edit Mode and can edit the page you visit, you’ll see light blue outlines around content, like this:</p> <p><a href="/link/c11df516584c496884cc4f11a4e92878.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="/link/61484a3a3ebe4be29e338a08e9d7f971.png" width="345" height="292" /></a></p> <p>(Note that as of version 7.5, they also include that handy tab which shows you the name of the property you’re editing. Therefore, this is an easy way to tell if you’re on a CMS7 or CMS7.5 site.)</p> <p>Effectively, every page you visit is loaded in an edit form and ready to be edited at any time. Unlike prior versions of EPiServer (and unlike a lot of other CMS), you don’t deliberately click an “Edit” link and move to a different view of the content (an edit form). Rather, you’re always in a “potential edit form.” At any time, you can click on a property (highlighted by the blue line, remember), and make a Property Edit. Every page you visit is standing at the ready, waiting to be edited.</p> <p>The act of making the first Property Edit to a Published Version of content creates a new Draft Version of the content item.</p> <p>After making your edit, if you go to the version list, there will be a “Draft” version in “front” of the published version.  If you go back to the “classic” Edit Mode (CMS6) the “Versions” tab will have a “(1)” on it, representing the number of unpublished versions ahead of the published version.</p> <p><a href="/link/831d355fbad14517aaa63ba48a592f4d.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="/link/0f5cfb2d418f4ec39cf4f0bfee5cecee.png" width="418" height="129" /></a></p> <p>Other editors can also see this version. They can select it, and view it in Edit Mode.</p> <p>Making any Property Edit will trigger the autosave process. You can see the “Saving…” text at the top of the page. When it’s complete, this will indicate that the content has been saved, the time it was saved, and offer a link to Undo which drops down this interface (we’ll talk about the “undo” functionality at length below).</p> <p><a href="/link/dfe25651fd8d41908e4f8e1a638346b5.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="/link/485ae3ad3a8a4d7b8fd58404c1dfd30e.png" width="294" height="170" /></a></p> <p>Additional Property Edits (after the first, which created the Draft Version) <em>do not create additional versions</em>. You are still working with your same Draft Version. Each Property Edit is saved back to this version, and other editors can see these changes.</p> <p>A completed Property Edit does two things –</p> <ol> <li>Saves the current state of the content (with your change) back to the Draft Version in the repository on the server. </li> <li>Saves the specific change you just made to a Javascript object in the browser. (This sounds technical, but we’ll explain why it’s important below.) </li> </ol> <p>The first point means that other editors can see these changes as you make them, because every Property Edit triggers a save all the back to the repository with the latest state of the content. Consequently, the Draft Version on the server always reflects the sum total of all the Property Edits you’ve made to any moment in time.</p> <p>If any editor visits the page in view mode, they will see the Published Version.  If they switch to Edit Mode, they will then see the Draft Version reflecting the current sum total of all Property Edits.  If they keep refreshing as you edit, they will see your accumulated edits.</p> <p>(An enormously handy change in CMS7 also gave editors a persistent URL for the current page in Edit Mode.  This means you can copy the URL out of the browser and email it to someone to look it – call it very low-tech approval process. While it shouldn’t be confused with workflow, editors have <em>loved</em> this feature, in our experience.)</p> <p>The second point there is important for a two reasons – </p> <p>First, it means that all of your Property Edits are saved separately, and in sequence. This is where things might get a little confusing, because each of the Property Edits is like a little “subversion” of your Draft Version.</p> <p>You can undo your Property Edits independently and in reverse sequence, by clicking “Undo…” (this is called the “undo stack”). If you change the Title, then the MainBody, then the Title again, you can back these changes out in reverse order: (1) the second change to the Title, (2) the change to the MainBody, then (3) the first change to the Title. Remember that the two changes to the Title constitute two separate Property Edits (with another one in between, even), so they’re undone separately.</p> <p>Second, it’s important is to note that these Property Edits are stored in Javascript in the browser, so they are only valid during that Editing Session which, remember is <i>that current document in that browser window at that moment</i>. This is important because it’s very easy to lose the undo stack – if you refresh the document loaded in the browser, or leave it and come back, you’re going to start a new Editing Session and consequently lose the previous undo stack.</p> <p>If you do leave the page you’re editing and come back to it, there’s no “Autosaved” text at the top. Since this is the only way you can access the “Undo” interface, you can’t undo any further than this. So the beginning of the current Editing Session is effectively the “backstop” on how far you can undo. You can only undo to the first Property Edit you made when you loaded this page in Edit Mode and started making changes, or to 10 Property Edits, whichever comes first. </p> <p>(The undo button within TinyMCE is even easier to explain. The only memory it has is from when TinyMCE was loaded, which means you can only undo to the point when you open the editor for this specific Property Edit. Clicking onto a property for a new Property Edit, will load TinyMCE again, and it will have no undo stack.)</p> <p>It’s also worth noting that TinyMCE autosaves every 10 seconds if it detects a change (and then, curiously, one time after the last save), and again when you close the editor.</p> <h2>Submitting, Publishing, and Reverting Content</h2> <p>Once all desired changes have been made, the a notice in the top right will notify you that there are “Changes to be published.”  From here, you have several options:</p> <ol> <li>Publish Changes </li> <li>Schedule for Publish </li> <li>Ready to Publish </li> <li>Revert to Published </li> </ol> <p>In most cases, Publish Changes will be selected here 90%+ of the time.  (Note that if you don’t have rights to publish this content, the interface looks a little different – displayed and discussed below.)</p> <p><a href="/link/b5fae798187d4259bcad482bccd76212.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="/link/736f871f6260478db528958d8d27ce74.png" width="306" height="272" /></a></p> <p>Three of these options change the status of the Draft Version you’ve been working on  – </p> <p>If you select “Publish,” the Draft Version’s status becomes “Published Version” and that version is considered the public version of the content. The current Published Version becomes “Previously Published.”</p> <p>If you select “Schedule for Publish,” you’re asked to enter a time, and the Draft Version’s status becomes “Delayed Publish.” At the appropriate time, a scheduled job changes the status to “Published Version” and it becomes the public version of the content at that moment. The current Published Version becomes “Previously Published.” This also opens up new options, available until the scheduled publish time, which will be discussed below.</p> <p>If you select “Ready to Publish,” the Draft Version’s status becomes “Ready to Publish.” It is locked for editing, and editors with publish rights on that content will be allowed to approve or reject it via the interface discussed below.</p> <p>All of these events (except for “Revert to Published”) effectively close the Draft Version to future edits. In taking any of these actions, you are saying, “This version is sealed. Freeze a snapshot of it, and if someone wants to do anything else, it has to be in a new version.”</p> <p>“Revert to Published” is a little different than the rest, in that simply throws away the Draft Version you’re working on. If you select this and confirm, the Draft Version is deleted and the page in the browser reloads, which ends your Editing Session and thereby clears your undo stack.</p> <p>The Published Version remains just that, as if nothing ever happened, and you’re effectively back to where you started. Making a new Property Edit will be considered the first one again, which will trigger a new Draft Version as explained above, and the process starts over.</p> <p>(Mentally, it’s important to understand that this doesn’t actively do anything with the Published Version. There might be some feeling that it copies the Published Version into the Draft Version and publishes that. It does not. The operation might be better described as “Discard All Unpublished Edits” or, simply, “Start Over.”)</p> <p>Note that if you don’t have publish rights to content, the interface is reduced.</p> <p><a href="/link/be93bd2d748c442dbcb44666e79d7f80.png"><img title="Sky7C4F" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="Sky7C4F" src="/link/97d7e03f7d28432794121c886ea0c264.png" width="283" height="266" /></a></p> <p>As designed, Ready to Publish is essentially your only option to move the content forward.</p> <h2>Handling Content that is Ready to Publish</h2> <p>“Ready to Publish” is meant to signify that editing is complete, and this content is being submitted for some kind of approval (after all, if you didn’t need approval and could just publish the changes yourself, you would have done so).  As we’ll see below, the “Ready for Publish” status actually triggers a special API event, and thus is really designed for workflow and approval scenarios.</p> <p>When content has been marked “Ready to Publish,” new options are available.</p> <p><a href="/link/7cfa5ebcf8554ed7abb5d5e4cf1ff4cb.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="/link/8cc40037d54b48eca37135a4f4c9d305.png" width="318" height="247" /></a></p> <p>From here, selecting “Approve and Publish” operates the same as selecting “Publish,” explained above.</p> <p>As for “Reject Changes,” it might be fair to assume it just changes the status of the Draft Version back to “Not Ready” and lets you edit again…but it doesn’t. It actually changes the status to “Rejected” and <em>creates a fresh Draft Version</em>. This new Draft Version is “Not Ready,” and it can be edited normally as described above.</p> <p>This happens because, remember, the Draft Version was sealed when we said the content was Ready to Publish. Anything other edits from that point had to be in a new version.</p> <p>Additionally, rejecting content is an event which needs to have some historical record. If content was submitted for publishing, then rejected, there needs to be some record that this occurred, hence the label change to “Rejected” and the new version. (It would be helpful at this point to allow a note to be entered indicated the reason for rejection – which would make this ideal for pseudo-workflow – but, alas, this is not possible. You can only reject without comment.)</p> <h2>Handling Scheduled Content</h2> <p>While content is scheduled to be published, you have two options for it:</p> <ol> <li>Remove Scheduling and Edit </li> <li>New Draft from Here </li> </ol> <p>(Note that in our experience, usage of both these options is rare.)</p> <p><a href="/link/79afc16475744d4cac322e3b4ae5b77f.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="/link/74146a198c654311b911385d12c79156.png" width="329" height="219" /></a></p> <p>Selecting “Remove Scheduling and Edit” will reject the scheduled version, as discussed above – the status changes to “Rejected” and a new Draft Version is created as “Not Ready.” (Admittedly, this may seem harsh – “rejecting” something when all you want to do is fix a small typo, for instance. Additionally, you now have to reschedule it, so be sure you know the time it was scheduled for before you select this option.)</p> <p>Selecting “New Draft from Here” will create a new Draft Version “ahead” of the scheduled version. This is for when you want to make changes to your content to be published after the scheduled version (so, you want to work two versions ahead of the currently published version).</p> <p>(What’s interesting is that you can also schedule <i>that</i> version, and you’re free to schedule it to be published prior to the other scheduled version. So, the schedule of version publishing gets out of sequence with the version numbers – Version #8 might be published earlier than Version #7, for instance. While odd, there’s no real harm done in this scenario, it’s just a little confusing. Just remember that the version list is according to the version number, not the date they were published.)</p> <h2>Appendix: API Events During the Editorial Cycle</h2> <p>(Note: this section is fairly developer-centric. If you have no interest in writing code for EPiServer, it can be safely skipped.)</p> <p>There are four major classes of events in the content lifecycle</p> <ol> <li>Create </li> <li>Save </li> <li>Check-in </li> <li>Publish </li> </ol> <p>Each event has a pre-event (“ing”) and a post-event (“ed”). They always occur in pairs – the pre-event right before the action; the post-event right after.</p> <p>The Create events (CreatingContent/CreatedContent) occur when content is first created, and never again. These events do not occur on new version creation.</p> <p>The Save events (SavingContent/SavedContent) occur every time the page is saved, which means on every single Property Edit. More specifically, they occur every time the “Saving…” text appears at the top of the page. So, if the editor is working in TinyMCE, these events will fire every 30 seconds or so. Additionally, these events occur immediately before the Check-In events, described below.</p> <p>The Check-in events (CheckingInContent/CheckedInContent) occur when the content is designated as Ready to Publish or Schedule for Publish. Immediately before they occur, the Save events are triggered. Oddly, the Check-In events do not fire if the content is simply published directly. Rather, they are meant for content that has been moved to a state where it is waiting to be published. Consequently, these events are primarily designed to trigger workflows and approval processes.</p> <p>The Publish events occur when content is published, by whatever method.  (In practice, this will be the event most often subscribed to, by a large margin. Usage of the other events is rare by comparison.)</p> <p>Here is the series of events for a piece of content that is created, goes through one Property Edit, is designated as Ready to Publish, and is then published.</p> <ol> <li>CreatingContent </li> <li>CreatedContent </li> <li>SavingContent (triggered by the PropertyEdit) </li> <li>SavedContent (trigged by the PropertyEdit) </li> <li>CheckingInContent </li> <li>SavingContent (triggered by the status change) </li> <li>SavedContent (triggered by the status change) </li> <li>CheckedInContent </li> <li>PublishingContent </li> <li>SavingContent (triggered by the status change) </li> <li>SavedContent (triggered by the status change) </li> <li>PublishedContent </li> </ol> <p>Note that the Creating event will not have a valid content reference (it will be 0), because the item has not been created yet (it hasn’t been saved back to the database).  The Created event will have the correct content reference.</p> <p>Also note that the Save event pairs are called three times – once due to the Property Edit, and once during the status change when CheckedIn, and once during the status change when Published.</p> <p>If this content was edited later, there would be two differences:</p> <ol> <li>The Create events would not occur. They occur only once in the entire lifecycle of a piece of content. </li> <li>The Save events would be called twice on the very first Property Edit. Since the first Property Edit creates a new version, that version is saved, then the Property Edit itself is saved. There’s an important difference between the two events: the first Saving event would get a reference to the Published Version. The Saved event that immediately follows would have the new Draft Version’s reference instead. </li> </ol> <p>It should be obvious at this point that you need to be careful with code in the Save events. These events get called often, and could easily occur a dozen times during a single Editing Session. Ensure code that runs during the Save events is both efficient and idempotent – it can be run multiple times with no ill effects on other resources.</p> <p>Again, remember that TinyMCE autosaves every 10 seconds and then again when it closes.  In most cases, this will result in at least two Save event pairs for every Property Edit in TinyMCE (unless the editor is very quick and closes before the first autosave, which is within 10 seconds of the first change).  Keep in mind that an editor who spends 30 minutes actively writing in TinyMCE will trigger almost <em>400</em> separate events<em>.</em></p> <p>If content is scheduled, the Check-In events occur at the time of scheduling. The Publish events (and their corresponding Save events) occur at the scheduled time of publish.</p>Routing a Specific Page Request to a Specific Controller/blogs/deane-barker/dates/2013/12/Routing-a-Specific-Page-Request-to-a-Specific-Controller/2013-12-22T01:53:57.0000000Z<p>I’ve always appreciated EPiServer’s ability to be friendly to arbitrary code – if you have some application-ish functionality you need as part of your site, EPiServer will play nice with it without you having to jump through hoops.</p> <p>However, it’s really helpful to be able to “wrap” this code in a page, so that it can be part of the page tree.  Even apps running inside your site need crumbtrails, navigation, permissions, etc.  EPiServer has to know about them and know where they “live” in the page tree so that it can manage all the things around them.  Consider that the average master page or layout requires knowledge of the CurrentPage in dozens of different ways to render properly.</p> <p>I blogged about this several years ago: <a href="http://gadgetopia.com/post/7088">Using Proxy Content Objects for Non-CMS Content</a>. That blog post is worth reading as it explains the problem and solution, which is universally valid for every CMS.</p> <p>Sometime after that, I built a simple plugin for EPiServer (webforms) that allowed you to direct specific page requests to specific templates, which I blogged about here: <a href="http://world.episerver.com/Blogs/Deane-Barker/Dates/2010/12/Selectively-Overriding-Page-Type-Template-Mapping/">Selectively Overriding Page Type Template Mapping</a>. This plugin used RewritePath to rewrite the inbound request to go to the webform of your choice.</p> <p>I’ve now done the same thing for MVC, except using a custom controller rather than a custom webform. On your page type, you can have a property that indicates what controller you want to use for that specific page (named "CustomController," by default). Inbound requests to that page will get redirected to that controller.</p> <p>You can use this to build an application that has its own controller and views, and then "wrap" that application in a page by creating a page and redirecting it to your controller. (Yes, you could do this by creating a new page type with that controller and a renderer, but creating one-off page types just to do that is messy.)</p> <p>For instance, say you build an application to view your log files. Then create a page, and specify "Log" as the custom controller for it (the suffix "Controller" is automatically added). Now, all requests for that page -- no matter where in the page tree it lives -- will get routed through LogController.</p> <p>(Clearly, you need some security around this property. It needs to be on a secure tab, or have some other method to ensure the average editor doesn't do something silly. Either that, or change the logic to some other method than a user-entered value. I leave that problem to you to solve...)</p> <p>The code is straightforward -- just compile it in. If your page doesn't have the "CustomController" property or if it doesn't have a value, the request will pass right through.</p> <p>It’s well-commented, so it should be easy to modify, and it’s also a good example of both an InitializableModule and how to hook the template resolution engine.</p> <p><strong>Update:</strong>  I added a way to specify the Action as well by using a slash between controller and action name.  So, a value of “Log” will send the request to the “index” action of the “LogController.”  However, a property value of “Log/Display” will send the request to the “Display” action of the “LogController.” <p><a href="http://pastie.org/8570620" target="_blank">Download CustomControllerResolver.cs</a></p></p>Using Custom Attributes from the Property Control in your Custom Controls/blogs/deane-barker/dates/2011/1/Using-Custom-Attributes-from-the-Property-Control-in-your-Custom-Controls/2011-01-26T18:04:53.0000000Z<p>When you write a custom property, you often end up writing a custom control for it too.  Then, when you call that property using the EPiServer Property control, it creates your internal (“inner”) control as part of its controls collection.  Your custom control might create other controls to actually render something.</p> <p>For instance, when you use the PageLink property from the Property control, the actual control tree looks like this:</p> <ul> <li>Property <ul> <li>PagePropertyReferenceControl <ul> <li>Hyperlink </li> </ul> </li> </ul> </li> </ul> <p>What frustrated me is that I couldn’t figure out how to get a value “down” to the “lower” controls.  I wanted to be able to set random values on the EPiServer Property control (the first level), then access those from my custom control (the second level), to set values on the child controls it creates (the third level).</p> <p>For instance, I wrote a custom property for “Gravatar Image.”  It displays the <a href="http://gravatar.com/">Gravatar</a> image for the last editor of the current page.  You drop it on the page using the standard Property control (the first level in the structure shown above).</p> <code><EPiServer:Property PropertyName=”LastEditorImage” runat=”server”/></code> <p>This creates my custom GravatarImageControl as a child control (the second level).  That, in turn, creates an Image control (the third level).  Like this:</p> <ul> <li>Property</li> <ul> <li>GravatarImage</li> <ul> <li>Image</li> </ul> </ul> </ul> <p>However, Gravatar lets you send a size parameter in the URL when requesting the image (“?s=50” or something).  I wanted to be able to set this on the EPiServer Property control, and have it be passed down and eventually find its way onto the ImageUrl property of my Image control..</p> <p>This is not that hard, it turns out.</p> <p>From inside my GravatarImageControl, I can get a reference to the “owning” Property control:</p> <code>Property owningPropertyControl = (Property)Parent;</code> <p>On that, you’ll find an Attributes collection, from which you can reference anything.  So, I can insert my Property control like this:</p> <code><EPiServer:Property PropertyName=”LastEditorImage” PixelWidth=”50” runat=”server”/></code> <p>And from inside my GravatarImageControl, I can “pick up” that value and use it to affect the Image control I'm creating:</p> <code>Property owningPropertyControl = (Property)Parent; <br />if (owningPropertyControl.Attributes["PixelSize"] != null) <br />{ <br />    image.ImageUrl = image.ImageUrl + "?s=" + owningPropertyControl.Attributes["PixelSize"]; <br />}</code> <p>Thanks to <a href="http://world.episerver.com/System/Users-and-profiles/Community-Profile-Card/?currentid=jakh">Jacob Kahn</a> for helping me figure this out.</p>Selectively Overriding Page Type Template Mapping/blogs/deane-barker/dates/2010/12/Selectively-Overriding-Page-Type-Template-Mapping/2010-12-08T13:26:46.0000000Z<p>One thing which has always been a mild frustration to me is EPiServer’s template mapping.  The concept of each Page Type only having one template always seemed restrictive.  The concept works fine in 95% of cases, but the other 5% can be a pain.  What if you have one page you want to do something a little (or a lot) differently than other pages of the same Page Type?</p> <p>In these cases, the obvious solution is to create a new Page Type and specify a different template for it.  But I hated the idea of creating a new Page Type just to get a new ASPX file to render.  You have to copy all the properties over to the new type, and you now have to keep the two Page Types in sync.  With one extra type, it’s not so bad.  When you get a dozen or so, it becomes a problem.</p> <p>Furthermore, right now we have a client coming off a <a href="http://www.serena.com/">Serena Collage</a> install, managed in Dreamweaver.  (For those not familiar, Collage was really an SCM system, re-purposed as a CMS.  You didn’t manage “content,” as much as you managed raw HTML files.  Think Subversion, but with some CMS-ish functionality wrapped around it.  Collage was discontinued and <a href="http://www.realstorygroup.com/Blog/1179-Serena-Collage-to-go-off-into-the-sunset/">went unsupported</a> two years ago, and I don’t think we’ll ever see another CMS quite like it.)</p> <p>Consequent to this experience, the client’s Web guy is very hands-on, and has created many landing pages which are painstakingly hand-crafted HTML.  To make their transition to EPiServer less painful, we needed to give him the ability to “escape” from type-bound templates whenever he felt the need, and be able to hand-write HTML in his editor of choice.</p> <p>So, I set out to find a way to be able to specify the template for a particular page.  I originally started down the road of <a href="/link/fc563eb68a32433c9af81e6700802093.aspx?epslanguage=en">creating a bizarre contraption of dynamically-loaded User Controls</a>, but I ended up just extending the URL rewriter, which was much easier than I expected.</p> <p>My TemplateOverrideRewriteProvider allows you to do two things:</p> <ol> <li>Specify a custom ASPX file in a property called “CustomTemplate” (add it to your Page Types as a simple string property).  If this property exists and has a value, the URL rewriter will send the request there. </li> <li>Put an ASPX file in a specific directory, named for the page ID.  If this file exists (ex: “27.aspx”), requests for that page ID will use that ASPX file.  (Essentially the same as above, but you enable it by creating a file, rather then specifying the file name.) </li> </ol> <p>The thing that made it easy is that we’re not hacking the <em>whole</em> URL rewriter.  In fact, we’re letting the core URL rewriter do its thing, we’re just tacking on some extra stuff at the end.  So, after we’ve called the base rewriting functionality, then we check to see if our page has a custom template specified, or if there’s an APSX page named for the ID.  If there is, we rewrite a little further to accommodate it.</p> <p>The actual mechanics of it are really simple – the “ConvertToInternalInternal” (say that five times fast…) method takes an incoming UrlBuilder object.  This is where the request is currently heading.  <em>You can do whatever you want with this</em>, primarily by changing the Path property.  Change it to whatever you like, an IIS will send the request there instead.</p> <p>This is in the works for us on a couple of projects.  We’re going to use it for situations where we need “application-ish” pages – like search results pages, for example – but we have a lot of logic and properties bound up in our standard Page Type, and we don’t want to recreate and manage a new Page Type just to enable a new template.  I’m thinking this will cut down our necessary Page Types on our average install by at least half.</p> <p>To use it, compile it into your project, and register it in episerver.config.  You’ll need to add an element for it under “episerver/urlRewrite/providers” and specify it as the “defaultProvider.”  Check the comments in the code also, as there’s a little configuration – you need to specify the directory paths you want to use for your custom templates.</p> <p>To represent the new concept behind this more appropriately, we’re dividing our template directories up like this:</p> <code>/Templates/ByPageType <br />/Templates/ByPageId <br />/Templates/ByCustomMapping</code> <p>The “ByPageType” directory is where the previously-standard templates went, to represent Page Types.  “ByPageId” contains templates named for page IDs, and “ByCustomMapping” contains templates that editors might enter in the “CustomTemplate” property.</p> <p> </p> <p><a href="http://blendinteractive.com/global/labs/TemplateOverrideRewriteProvider.zip">TemplateOverrideRewriteProvider Download</a></p>Integrated Lucene Search for EPiServer/blogs/deane-barker/dates/2010/12/Lucene-Search-for-EPiServer/2010-12-07T03:47:58.0000000Z<p>We recently implemented a site that had more PDF files than pages, in a ratio of about 6-to-1.  Sadly, this process revealed some of the shortcomings in EPiServer’s default search architecture.</p> <p>The biggest problem we found is that there are two search algorithms at work.  Pages are indexed via a custom-built tokenizer, with their keywords stored in a database table.  Files, on the other hand, are indexed and searched via Lucene.Net.</p> <p>Given that there are two separate methodologies, it becomes somewhat impossible to mix results.  When using the SearchDataSource, what happens is that all page results are returned first, then all file results come after.  This is a significant issue for my client.  Given their mix of content formats, visitors are often looking for a PDF file first, and an HTML page second.</p> <p>There is a “magic” property on all search result pages called “PageRank.”  We were excited when we found this, because we figured we could use LINQ to order by this property.  Unfortunately, this property is fairly crippled because all files have the value of “500.”  We reflected through the DLL and found that this is hard-coded and not changeable.</p> <p>When we brought this to their attention, EPiServer offered to do a hotfix, but we weren’t sure it would help – even if this value wasn’t always the same, how do we know if the PageRank value for files has a directly proportional relationship to the PageRank value of the page results?  These two values are calculated completely differently, and we have no way of knowing if they compare to each other in any meaningful way.  (ex: if a page result has a PageRank of 500 and a file result has a PageRank of 1000, does it follow that the file is twice as relevant than the page?)</p> <p>So, the bottom line is that we needed to get pages and files indexed and searched using the same algorithm.  This way, we can compare them are know that the relative values have some relationship.</p> <p>Files are indexed via the EPiServer Indexing Service, and this offered the fewest options to change.  So, we decided to keep that as-is, and simply created another Lucene.Net index for pages.  Then, we abandoned EPiServer’s SearchDataSource control, and implemented a new searching API that uses raw Lucene.Net calls to query both sets of indexes – those from files (created by the EPiServer Indexing Service) and pages (created by our new plugin).</p> <p>Code to search looks like this:</p> <code>LuceneSearchManager search = new LuceneSearchManager(); <br />search.SearchIndexes = "SitePages,SiteGlobalFiles,SiteDocuments"; <br /><br />LuceneSearchResultCollection resultCollection = search.Search("episerver"); <br /><br />SearchResults.DataSource = resultCollection.Results; <br />SearchResults.DataBind(); </code> <p>In this situation, “SitePages” is an extra VPP created just to store the Lucene index of pages.</p> <p>Preliminary tests have been quite good.  Both files and pages are indexed and searched using the same methodology, and result sets are ordered by rank, regardless of whether they’re page or file results.  The API supports paging, and also supports EPiServer’s security model (the actual enforcement of security was modeled after how EPiServer does it in SearchDataSource – it catches an expected exception at one point, which I’m not in love with, but it works).  PageTypeNames and all ancestor IDs back to the Start Page are stored, and can be used for searching and filtering as well.</p> <p>Additionally, since you have low-level access to the underlying Lucene.Net index, you have the full power of Lucene.Net at your disposal, including search-time field boost to bias results.</p> <p>This has not been deployed to production yet, but will likely see the light of day in Q1 2011.  Consider it an alpha release.  If you implement, we welcome all feedback.</p> <p><a href="http://blendinteractive.com/global/labs/LuceneSearch.zip">Lucene Search Download</a></p>Simple Token Replacement/blogs/deane-barker/dates/2010/11/Simple-Token-Replacement/2010-11-15T20:13:11.0000000Z<p>Someone posted <a href="/link/b1bf52beb2c143be8056a81582f629be.aspx?epslanguage=en">something</a> to the forums in which they’re solving a problem I’ve had in the past using Dynamic Content:</p> <blockquote> <p>[we used] DynamicContent property to display a site wide value on the web page.</p> </blockquote> <p>So, Dynamic Content is used as a placeholder, essentially, which gets swapped with some value at render time.  We’ve run into this several times.</p> <ul> <li>One of our clients needed to refer to a specific registration fee all over the Web site.  This fee amount changed every year, and they didn’t want to have to track down where it was posted every year.</li> <li>One of our clients had 86 sites on the same install, and each one would fetch a Contact Us page from a common location.  But this page had to have different phone numbers, etc. for each of the 86 sites.</li> </ul> <p>We started using Dynamic Content for this, but we found it was a little heavy-handed.  Our clients wanted something simple and fast.  Additionally, you can’t see what value you are replacing from the editor.  You just see the generic “Page Property” placeholder, but you have no idea which property you are dumping there.  Finally, in the second situation, the editor couldn’t pick the specific page from which to pull the property for the Page Property plugin – they just wanted the property from the <em>current Start Page</em>, whatever that happened to be for this particular site.</p> <p>So, we decided to go the wiki-like route and just have the client embed a text pattern which we would catch and replace at render time.  Something like this:</p> <code>[[CustomerServicePhone]] <br />[[RegistrationFeeAmount]]</code> <p>We mapped these to properties on the Start Page.  So, at render time ”[[CustomerServicePhone]]” gets replaced with the contents of the “CustomerServicePhone” property on the Start Page.  (We use Start Page properties all the time to hold site-wide values, since there will only ever be one Start Page.)</p> <p>The code to execute this is in a PageAdapter mapped to System.Web.UI.Page, like this:</p> <code><adapter controlType="System.Web.UI.Page" adapterType="BlendInteractive.PageAdapters.TokenReplacer" /></code> <p>By doing this as a Page Adapter rather than on the Property control, we catch this token everywhere it’s used, whether it’s coming out of a Property control, is hard-coded into the template, is written to the browser from code-behind, whatever.</p> <p>This has worked well for us on the two projects where we’ve used it.  A few things to know:</p> <ul> <li>We normally just use String properties, but you could technically use any type of property that renders to a string.  In testing, I’ve used everything from Page properties to Link Collections.</li> <li>If you need to do this replacement from code that doesn’t output to a browser – like a scheduled job, perhaps – you’ll need to extract the logic to a static class and call that class from both the adapter and your code.  Wouldn’t be hard to do, but we’ve never had to do it. </li> <li>Security has never been a concern for us with this particular functionality, but if you want to make sure editors can only use certain properties as replacement tokens, I would just implement a naming convention.  Perhaps say all “replaceable” properties have to start with “Token-“ and then adjust the regex to look for that pattern only.</li> </ul> <p><a href="http://blendinteractive.com/Global/labs/TokenReplacer.zip">TokenReplacer.cs</a></p>A Simple ROBOTS.TXT Handler in EPiServer/blogs/deane-barker/dates/2010/9/A-Simple-ROBOTSTXT-Handler-in-EPiServer/2010-09-21T14:00:40.0000000Z<p>We’re moving a client from a Dreamweaver-based site to an EPiServer site.  One of the only reasons they had left to FTP into the site at any time was to manage the robots.txt file.  We wanted to eliminate that reason so we could shut FTP off completely.</p> <p>This is what we did:</p> <p>1. Added a Long String property on the Start Page called “RobotsTxt”.  We dumped the contents of the existing robots.txt in there.</p> <p>2. Add this line to our web.config:</p> <code><add name="RobotsTxtHandler" preCondition="integratedMode" verb="*" path="robots.txt" type="Blend.EPiServer.RobotsTxtHandler, [Assembly Name]" /></code> <p>3. Compiled this class into our project:</p> <code>using System.Web; <br />using EPiServer; <br />using EPiServer.Core; <br /> <br />namespace Blend.EPiServer <br />{ <br />    public class RobotsTxtHandler : IHttpHandler <br />    { <br />        public bool IsReusable { get { return true; } } <br /> <br />        public void ProcessRequest(HttpContext context) <br />        { <br />            context.Response.ContentType = "text/plain"; <br />            context.Response.Write(DataFactory.Instance.GetPage(PageReference.StartPage).Property["RobotsTxt"].ToString()); <br />        } <br />    } <br />} </code> <p>Now, the client edits their robots.txt text in EPiServer. Calling “/robots.txt” will dump the contents of that Start Page property into the output as plain text.</p> <p>(The larger principle at work here is that the Start Page is generally treated as a Singleton in EPiServer – there will only ever be one per site.  This means we tend to load it up with global properties – data you just need to store and manage in EPiServer, but will never publish as its own page.  Things like this robots.txt text, the TITLE tag suffix for the site, random Master Page text strings, etc.)</p>Correcting All Raw URLs Inside a String of HTML/blogs/deane-barker/dates/2010/9/Correcting-All-Raw-URLs-Inside-a-String-of-HTML/2010-09-03T23:04:19.0000000Z<p>If you create a link inside an EPiServer page to another EPiServer page, the link that gets embedded in the HTML is not friendly.  It looks something like this:</p> <code>/Templates/PageTypes/TextPage.aspx?id=66</code> <p>EPiServer corrects these links very late in the page lifecycle – actually in a response filter after the entire page has been generated.  This way, it’s sure to correct links no matter where on the page they might be.</p> <p>What I noticed, however, is that these links were only getting corrected for Web forms.  More specifically, they were only getting corrected for anything with a “text/html” content type.</p> <p>This was usually fine because I could change the response type, but sometimes it was an issue, especially when doing Ajax callbacks.  You often send those back as some plain text format like JSON, and you form the response at the code level, concatening the raw values of properties into a string.  Before long, you notice that your links aren’t corrected in blocks of HTML.</p> <p>It’s not hard to get the friendly URL from a PageData object:</p> <code>UrlBuilder url = new UrlBuilder(pd.LinkURL); <br />UrlRewriteProvider.ConvertToExternal(url, somePageData.PageLink, UTF8Encoding.UTF8); <br />string theFriendlyURl = url.ToString(); </code> <p>This is great to go from a PageData object to a friendly URL.  What I found to be much tricker, however, was if I had a big string of HTML with a bunch of embedded links and <em>I needed to correct them all before working with the string</em>.</p> <p>When I was in Sweden for the Partner Summit, I posed this question to <a href="http://world.episerver.com/System/Users-and-profiles/Community-Profile-Card/?encryptedcurrentid=QYwFp7cfOy8%3d">Magnus Stråle</a>.  I was awfully grateful when he took the time to dig through EPiServer’s unit tests to find a chunk of code for me.  It’s distilled down to this function:</p> <code>private string CorrectLinks(string rawHtml) <br />{ <br />    var toExternal = new FriendlyHtmlRewriteToExternal(UrlBuilder.RebaseKind.ToRootRelative); <br />    return toExternal.RewriteString( <br />        new UrlBuilder(HttpContext.Current.Request.Path), <br />        new UrlBuilder(HttpContext.Current.Request.RawUrl), <br />        HttpContext.Current.Response.ContentEncoding, <br />        rawHtml); <br />}</code> <p>This will take a string of HTML, correct all the links inside of it, and return another string, ready for insertion into any response, be it text, JSON, or whatever.</p> <p>Thanks to Magnus for taking the time to help me solve this.</p>Automatically Naming Pages/blogs/deane-barker/dates/2010/9/Automatically-Naming-Pages/2010-09-02T19:34:07.0000000Z<p>Here’s a fact -- not every page needs to be explicitly named.  I mean, <em>they all have to have names</em>, but you shouldn’t have to manually enter them all the time.</p> <p>Often, a page should have its name derived from other properties.  For instance, if you have a page representing a person, you’re probably going to have separate fields for FirstName and LastName.  By logical extension then, the page name should be something like “FirstName LastName,” or “LastName, FirstName.”  You don’t want the editor to have to enter three things – the first name, the last name, and the page name…which is the first name and the last name all over again.</p> <p>Now, you can set page names from code.  Just catch the PageCreating or PageSaving event, and set the value of e.Page.PageName, no problem.  But this is less than ideal because the pesky “Name” field is still in Edit Mode just begging for some input.  This looks tacky (“Yeah, you have to put something in there…but we’re not going to use it…so, it doesn’t really matter…”), and it’s potentially confusing for editors (“I entered X there, and now its set to Y…”).</p> <p>So, how do you get rid of it?  I asked this question <a href="/link/a9322efb265844659f153396fb30cd60.aspx?epslanguage=en">in the forums</a>, and Linus showed me how it’s done.</p> <p>The EditPanel object throws an event, which you can hook to modify the page that’s loading into it.  What you do is just change the DisplayEditUi boolean on the Pagename property.  You can do this in Application_Start, like this:</p> <code>protected void Application_Start(Object sender, EventArgs e) <br />{ <br />    EPiServer.UI.Edit.EditPanel.LoadedPage += new EPiServer.UI.Edit.LoadedPageEventHandler(EditPanel_LoadedPage); <br />} <br />private void EditPanel_LoadedPage(EPiServer.UI.Edit.EditPanel sender, EPiServer.UI.Edit.LoadedPageEventArgs e) <br />{ <br />    e.Page.Property["Pagename"].DisplayEditUI = false; <br />}</code> <p>This works great, but since I had 3-4 page types that needed to be handled, I built it out a little further.  I link to a class file below that you’re welcome to use.  In it, you have a List of PageTypeNames that should have their name field suppressed, like this:</p> <code>private static List<string> affectedPageTypes = new List<string>() { "Council Member", "Periodical" }; </code> <p>Additionally, there’s a switch statement lower down where you can put the logic to derive your page names based on type:</p> <code>// Modify the code below to custom-name your pages <br />switch (e.Page.PageTypeName) <br />{ <br />    case "Council Member": <br />        // Example: "Barker, Deane" <br />        pageName = String.Format( <br />            "{0}, {1}", <br />            e.Page.Property["LastName"].ToString(), <br />            e.Page.Property["FirstName"].ToString()); <br />        break; <br /> <br />    case "Periodical": <br />        // Example: "2010: Vol. 32, No. 2" <br />        pageName = String.Format( <br />            "{0}: Vol. {1}, No. {2}", <br />            e.Page.Property["Year"].ToString(), <br />            e.Page.Property["Volume"].ToString(), <br />            e.Page.Property["Number"].ToString()); <br />        break; <br />} </code> <p>It’s a single class file.  Change the code as-needed, and compile it into your project.  (Normal warnings apply –  I wrote this about an hour ago, and it’s only been tested for about five minutes…)</p> <p><a href="blendinteractive.com/global/labs/AutoNamer.zip">AutoNamer.zip</a></p>Adding Canonical Links for Language-Specific URLs/blogs/deane-barker/dates/2010/7/Adding-Canonical-Links-for-Language-Specific-URLs/2010-07-01T07:45:31.0000000Z<p>We’re doing an multi-lingual site, and one of the requirements is that we put the language identifier in the URL.  So URLs will look like:</p> <blockquote> <p style="font-family: courier new, monospace">/en/some-page <br />/fr/some-page</p> </blockquote> <p>And, of course, this URL is also valid:</p> <blockquote> <p style="font-family: courier new, monospace">/some-page</p> </blockquote> <p>This always makes me nervous because I start worrying about Google and other search crawlers.  I don’t want content indexed under more than one URL.</p> <p>Google has a solution for this -- their “canonical” link tag, <a href="http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html">as described here</a>:</p> <blockquote> <p>If your site has identical or vastly similar content that's accessible through multiple URLs, this format provides you with more control over the URL returned in search results. It also helps to make sure that properties such as link popularity are consolidated to your preferred version.</p> </blockquote> <p>So, I wrote up a little page plugin to automate this.  This code executes during PreRender for any EPiServer content.  It gets the list of enabled languages, and if the RawUrl begins with any of the enabled languages (“/en/”, “/fr/”, etc.), it inserts a LINK tag in the header with the non-language specific URL.</p> <p>So a request for this --</p> <blockquote> <p style="font-family: courier new, monospace">/en/some-page</p> </blockquote> <p>-- would result in this being added to the header:</p> <code><link href=”/some-page” rel=”canonical”/></code> <p>A small thing, but important for all the reasons Google mentions in the article linked above.</p> <p><a href="http://blendinteractive.com/global/labs/AddCanonicalLinkTag.zip">Here’s the code</a>.  It’s a single class file.  Compile it into your project.  (And, all the standard warnings apply – hasn’t been production tested, use at your own risk, yada, yada…)</p>Redirecting to a Simple Address/blogs/deane-barker/dates/2010/5/Redirecting-to-a-Simple-Address/2010-05-14T20:44:07.0000000Z<p>I was doing an EPiServer demo the other day, and was the showing the “simple address” feature that lets you map pages to simple URLs like “/register” or “/info.”</p> <p>The client asked if those simple URLs could <em>redirect</em> rather than rewrite.  At the default, if you map a page to a simple address, it’s available both at that URL <em>and</em> its normal URL further down in the tree.  This could be problematic for some organizations to have duplicate content at two URLs.</p> <p>Just for fun, I set out to solve the problem, and managed to bang it out in a dozen lines of code (most of it error checking).</p> <p>The timing of where the code runs is a little tricky.  I thought about writing an HttpModule for it, but I needed to have the EPiServer page in hand when the code ran.  So, I need to run this code <em>before</em> anything on the page runs, but <em>after</em> the EPiServer content is mapped to the incoming request.  And it obviously need to run on every page, so I was looking for a way to catch the first moment we’re sure the visitor wants an EPiServer content object.</p> <p>The method I used was to write a PagePlugin that hooks into the Init event of the page, like this:</p> <code>public static void Initialize(int optionFlag) <br />{ <br />    PageBase.PageSetup += new PageSetupEventHandler(PageSetup); <br />} <br /> <br />public static void PageSetup(PageBase sender, PageSetupEventArgs e) <br />{ <br />    sender.Init += new EventHandler(CheckForSimpleAddressAccess); <br />} </code> <p>What we’ve done with this is hooked an event very early into the page lifecycle.  This runs before anything in the actual page code-behind, so we can essentially pre-empt the entire page execution.</p> <p>After checking that we’re on a TemplatePage with a valid (non-null) EPiServer page, I check to see if the RawUrl is the same as the simple address for the page.  If it is, I assume they came in on the simple address and redirect them to the <em>real</em> address.  The code for this is pretty simple:</p> <code>if (String.Concat("/", (sender as PageBase).CurrentPage.Property["PageExternalURL"].ToString()) == HttpContext.Current.Request.RawUrl) <br />{ <br />    Http.Current.Response.Redirect((sender as PageBase).CurrentPage.StaticLinkURL, true); <br />} </code> <p>I took it one step further and added the ability to have a “Redirect to Simple Address” checkbox, so you can select whether a simple address will act as a rewrite or a redirect on a page-by-page basis.  If that doesn’t interest you, it should be pretty simple to comment those lines out and just have this code execute in all cases.</p> <p>Just a quick warning: this code tested fine, but has not been implemented in any actual environment (I was just solving a problem for fun, remember), so should not be considered production-ready.  If you find any bugs, <a href="mailto://deane@blendinteractive.com">drop me an email</a>.</p> <p><a href="http://blendinteractive.com/Global/labs/RedirectSimpleAddress.zip">Here’s the download.</a>  The zip contains a single class file.  Compile it into your project.</p>EPiServerSearchMeta/blogs/deane-barker/dates/2009/9/EPiServerSearchMeta/2009-09-01T16:45:59.0000000Z<p>Over the last few years, I’ve done four implementations of the <a href="http://www.google.com/enterprise/search/mini.html">Google Mini search appliance</a>.  This is a piece of hardware (a 1U rack mount) that acts has a search crawler and engine.</p> <p>It crawls your Web site (or whatever else you point it at) 24 hours a day, and you can throw queries at it via a REST interface, and get results back as XML (you can also transform the XML on the device itself, and use it to actually present queries to the end user, but this is awkward and requires you to dupe your interface on another machine, which is never fun).</p> <p>The device is quite good for text-heavy search, and retails for $2,995, making it a cheap solution for a lot of situations.</p> <p>The Mini can do fairly granular searching of META (<a href="http://code.google.com/apis/searchappliance/documentation/46/xml_reference.html">search protocol reference</a>). Over the years, we’ve figured out that you should stack as many META tags as possible in your pages, because you never know what you’re going to want to search on.  If, for instance, your client wants to isolate a search to just news articles, then it’s helpful to have a META tag in there with the type of content (alternately, you could create a distinct collection in the device, but maintaining these can be tedious).</p> <p>For another CMS, we developed a control that dumped all sorts of META to the HEAD tag of the page.  We refined this over the years to only run for the Mini, since it got to the point where it was computationally expensive to find and return all this information, and we only needed it for the Mini (we didn’t need it for public search engines, for instance).</p> <p>For our first EPiServer/Mini integration, we adapted the control a bit, but the functionality is roughly the same – it dumps all sort of information to META tags, including any properties you might specify.</p> <p>Register it like this:</p> <code><%@ Register TagPrefix=”Blend” Namespace=”Blend.EPiServer.Controls” Assembly=”[insert your assembly name here"]” /></code> <p>Then put the control in the HEAD tag like this:</p> <code><Blend:EPiServerSearchMeta TagNameFormat="MySite.EPiServer.{0}" UserAgentString=”gsa” QuerystringCode=”OpenSesame” Properties="Title,Summary" runat="server" /></code> <p>It will only run when the currently executing page is of type TemplatePage (so, only for EPiServer templates that have a content object attached).</p> <p>The control outputs the following information:</p> <ul> <li>The page ID </li> <li>The page type ID </li> <li>The page type name </li> <li>The page name </li> <li>The parent page ID </li> <li>The parent page type ID </li> <li>The parent page type name </li> <li>Every page ID from the current page’s parent back to the start page (in multiple META tags) </li> <li>The depth of the page (the start page is 0, top level pages are 1, etc.) </li> </ul> <p>It looks like this:</p> <code><meta name="MySite.EPiServer.PageID" content="9" /> <br /><meta name="MySite.EPiServer.PageTypeID" content="7" /> <br /><meta name="MySite.EPiServer.PageTypeName" content="NewsArticle" /> <br /><meta name="MySite.EPiServer.PageName" content="Deane Saves the World" /> <br /><meta name="MySite.EPiServer.ParentPageID" content="8" /> <br /><meta name="MySite.EPiServer.ParentTypeID" content="5" /> <br /><meta name="MySite.EPiServer.ParentTypeName" content="NewsArchive" /> <br /><meta name="MySite.EPiServer.AncestorID" content="8" /> <br /><meta name="MySite.EPiServer.AncestorID" content="7" /> <br /><meta name="MySite.EPiServer.AncestorID" content="3" /> <br /><meta name="MySite.EPiServer.PageDepth" content="3" /> <br /><meta name="MySite.EPiServer.Category" content="7" /> <br /><meta name="MySite.EPiServer.Category" content="9" /> <br /><meta name="MySite.EPiServer.Category" content="13" /> <br /><meta name="MySite.EPiServer.Category" content="15" /> <br /><meta name="MySite.EPiServer.Category" content="16" /></code> <p>There are a few control attributes…</p> <p><strong>TagNameFormat</strong> is the format of the “name” attribute of the resulting META tag.  So, in the above example, the Page Type ID of the content will output as:</p> <code><meta name=”MySite.EPiServer.PageTypeID” content=”7”/></code> <p><strong>Properties</strong> is a comma-delimited list of properties you want to dump to META.  Be careful here, obviously – the entire text of the content object is unnecessary and potentially problematic.  The control will simply call ToWebString() on all of them, so make sure this outputs what you want.  Also, if the property is a Category selection, the control will split the IDs up under separate tags.</p> <p><strong>UserAgentString</strong> is used to identify the crawler. Enter a value in here that will be unique to the user agent string of your crawler – “gsa” works well for the Mini.  If the control finds this string it will execute, otherwise it will exit without doing anything.</p> <p><strong>QuerystringCode</strong> is a secret code you can use to debug the control.  If this value is found in a querystring argument called “show_meta,” the control will <em>always execute</em> (regardless of the user agent string). This is useful for debugging, so you can see the META it outputs.</p> <p><a href="http://labs.blendinteractive.com/projects/episerver/episerver-search-meta/source.zip">Get the Code</a> (.zip file, containing a single .cs file)</p>