Blog posts by Hans2012-05-01T16:08:11.0000000Z/blogs/Hans/Optimizely WorldEPiServer Workflow Replacement : Step 6 - Hooking the Ready to Publish Event/blogs/Hans/Dates/2012/5/EPiServer-Workflow-Replacement--Step-6---Hooking-the-Ready-to-Publish-Event/2012-05-01T16:08:11.0000000Z<p>This is the last in a series of posts about how my company built a replacement workflow platform for EPiServer. Why we chose to do this is explained here: <a href="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/">http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/</a></p> <p>I’ve covered why we had to build our own workflow. I’ve gone into how to disable the base Workflow tab and replace it with your own GuiPlugin named Workflow. I’ve explained from the top down how this all works from a user/admin perspective, I’ve shown what database fields need to be updated to reject pages – and I’ve given you a workaround to disable that pesky Save and Publish button.</p> <p>The only other critical component to this entire process is the ability  to notify approvers whenever a page is marked “Ready to Publish”. To do this, we create a new class and hook into the DataFactory.Instance.CheckingInPage event like so:</p> <p>First – I create a new class : I named mine CustomPageActionControl.cs. I then designate it as a [PagePlugIn] and set up the Initialize method..this is where we hook CheckingInPage – in this instance by calling an additional class called NotifyApprovers.</p> <pre class="language-csharp"><code> [PagePlugIn]
<span class="kwrd">public</span> <span class="kwrd">class</span> CustomPageActionControl
{
<span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> Initialize(<span class="kwrd">int</span> bitflags)
{
DataFactory.Instance.CheckingInPage += NotifyApprovers;
}
}</code></pre>
<pre class="language-csharp"><code> </code></pre>
<p>(As a side note, this is really handy for doing all sorts of other things..most of our sites also hook DataFactory.Instance.SavedPage, PublishedPage, DeletingPage, MovingPage, etc – there are all sorts of things you can hook into: <a title="http://sdk.episerver.com/library/cms5/html/AllMembers_T_EPiServer_DataFactory.htm" href="http://sdk.episerver.com/library/cms5/html/AllMembers_T_EPiServer_DataFactory.htm">http://sdk.episerver.com/library/cms5/html/AllMembers_T_EPiServer_DataFactory.htm</a> )</p>
<p> </p>
<p>Anyway..our NotifyApprovers method performs thusly:</p>
<ol>
<li>We get a working version of the page information that we want to send on – PageVersionURL, Approver User Names, etc.. </li>
<li>We iterate through an array of all access control entities, check their roles (in this instance they have to have LANGUAGE_EN-US or the correct locale access – so we don’t send our European users information about approving US Locale specific information) and getting a list of all users who can approve </li>
<li>We email out all approvers with information about the page (as seen in step #3)</li>
</ol>
<p> </p>
<p>The below <em>should</em> be cleaned up and made more DRY (i.e. take advantage of LINQ), but you get the idea.. </p>
<p> </p>
<pre class="language-csharp"><code> <span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">void</span> NotifyApprovers(<span class="kwrd">object</span> sender, PageEventArgs e)
{
String ApproverUserNames = <span class="str">""</span>;
List<String> EmailList = <span class="kwrd">new</span> List<String>();
String PageVersionUrl = ConfigurationManager.AppSettings[<span class="str">"BaseUrl"</span>] + e.Page.LinkURL;
PageVersionUrl = PageVersionUrl.Replace(<span class="str">"&epslanguage"</span>, <span class="str">"_"</span> + e.Page.WorkPageID + <span class="str">"&idkeep=True&epslanguage"</span>);
<span class="kwrd">foreach</span> (EPiServer.Security.RawACE Ace <span class="kwrd">in</span> e.Page.ACL.ToRawACEArray()) <span class="rem">// Get array of all access control entries</span>
{
EPiServer.Security.AccessControlEntry EAC = <span class="kwrd">new</span> EPiServer.Security.AccessControlEntry(Ace);
<span class="kwrd">if</span> ((Ace.Access & EPiServer.Security.AccessLevel.Publish) == EPiServer.Security.AccessLevel.Publish) <span class="rem">//Only look at users who have access to publish page</span>
{
<span class="kwrd">foreach</span> (System.Web.Security.MembershipUser AllUsers <span class="kwrd">in</span> Membership.GetAllUsers())
{
<span class="kwrd">if</span> (Roles.IsUserInRole(AllUsers.UserName, <span class="str">"LANGUAGE_"</span> + (Convert.ToString(e.Page.LanguageID)).ToUpper())) <span class="rem">// User has locale access</span>
{
<span class="kwrd">if</span> (Roles.IsUserInRole(AllUsers.UserName, Ace.Name))
{ <span class="rem">//user has publish access for this page</span>
<span class="kwrd">if</span> (!EmailList.Contains(AllUsers.Email.ToString())) <span class="rem">//only add users once</span>
{
ApproverUserNames += UserProfileTools.GetFullName(AllUsers.UserName.ToString()) + <span class="str">"; "</span>;
EmailList.Add(AllUsers.Email.ToString());
}
}
}
}
}
}
String EmailBody = <span class="str">"<h2>EPiServer Approval Requested: Page Ready to Publish</h2><table style=\"font-family: Arial; font-size: 12px; \"><tr><td><b>Title:</b></td><td>"</span> + e.Page.PageName + <span class="str">"</td></tr><tr><td><b>Submitter:</b></td><td>"</span> + UserProfileTools.GetFullName(e.Page.ChangedBy) + <span class="str">"</td></tr><tr><td><b>Version URL:</b></td><td>"</span> + PageVersionUrl + <span class="str">"</td></tr><tr><td><b>Published URL:</b></td><td>"</span> + ConfigurationManager.AppSettings[<span class="str">"BaseUrl"</span>] + e.Page.LinkURL + <span class="str">"</td></tr><tr><td><b>Necessary Action:</b></td><td>This page is ready for your approval. Please log into EPiServer and visit the <i>Ready to Publish URL</i> specified above. For your reference, the current published page version is also available by visiting the <i>Published Version URL</i>."</span> + <span class="str">"</td></tr><tr><td><b>Content Approvers:</b></td><td>"</span> + ApproverUserNames + <span class="str">"</td></tr></table>"</span>;
<span class="kwrd">if</span> (EmailList.Count() > 0)
EmailTools.SendTemplatedEmail(<span class="str">"EPiServer Page Approval Request – <a href="http://www.mysite.com">www.mysite.com</a>"</span>, EmailBody, EmailList, <span class="str">"EPiServer@episerver.com"</span>, <span class="str">"EPiServer"</span>);
}</code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p> </p>
<p>And that’s it! When an editor hits “Ready to Publish”, all of the approvers are automatically notified that they need to go in and check out the content.</p>
<p>That sums up our custom workflow application. If you found it at all useful in any respect (or if you feel like pointing out my errors in logic and/or code) I’d love to hear from you in the comments below.</p>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>EPiServer Workflow Replacement : Step 5–Bye bye, Save and Publish!/blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-5Bye-bye-Save-and-Publish/2012-04-30T23:22:14.0000000Z<p>This is the fifth in a series of posts about how my company built a replacement workflow platform for EPiServer. Why we chose to do this is explained here: <a title="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/" href="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/">http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/</a></p> <p>I hate the Save and Publish button. This disdain essentially boils down to two issues:</p> <ol> <li>Humans are fallible creatures, so assuming that a page is ready to go live without actually looking at it is just asking yourself for trouble. </li> <li>A lot of companies use EPiServer in single instance production environments. We don’t ..but a lot do. Do you really want people to update content live on the web without looking at it? </li> </ol> <p>That said, I do use Save and Publish in Development..and I get why it’s there…. I just wish there was a way to get rid of it through the application itself.</p> <p>For our team, Save and Publish had to go.</p> <ol> <li>In CMS6, navigate to :<strong> C:\Program Files\EPiServer\CMS\6.1.379.0\Application\UI\CMS\Edit\EditPageButtonControl.ascx</strong> - if installed on 64 bit it will usually be “Program Files (x86)” instead </li> <li>In CMS5, navigate to <strong>C:\Program Files\EPiServer\CMS\5.2.375.7\Application\UI\Edit\EditPageButtonControl.ascx</strong>  - again, append (x86) if on a 64 bit operating system</li> <li>In the SaveAndPublish EPiServer UI ToolButton, change RequiredAccess to “Administer” – this prevents people with publish access from accessing the SaveAndPublish button</li> <li>Add RequiredAccess=”Administer” to the Publish EPiServerUI:ToolButton as well. This forces people to go to the “Workflow” tab to publish pages.</li> </ol> <p> </p> <p>Here is how our CMS 6 EditPageButtonControl.ascx looks:</p> <p> </p> <pre class="language-csharp"><code><span class="asp"><%@ Control Language="C#" AutoEventWireup="false" CodeBehind="EditPageButtonControl.ascx.cs" Inherits="EPiServer.UI.Edit.EditPageButtonControl" %></span>
<span class="kwrd"><</span><span class="html">asp:Panel</span> <span class="attr">ID</span><span class="kwrd">="ButtonPanel"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">CssClass</span><span class="kwrd">="epitoolbararea epi-toolbarOnPage"</span> <span class="attr">Visible</span><span class="kwrd">="true"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButtonContainer</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="SaveOnly"</span> <span class="attr">OnClick</span><span class="kwrd">="SaveOnly_Click"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">SkinID</span><span class="kwrd">="Save"</span> <span class="attr">text</span><span class="kwrd">="<%$ Resources: EPiServer, button.saveonly %>"</span> <span class="attr">ToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, edit.editpanel.tooltipsaveonly %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="SaveButton"</span> <span class="attr">OnClick</span><span class="kwrd">="Save_Click"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">SkinID</span><span class="kwrd">="SaveShow"</span> <span class="attr">text</span><span class="kwrd">="<%$ Resources: EPiServer, button.saveandview %>"</span> <span class="attr">ToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, edit.editpanel.tooltipsave %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="SaveAndPublish"</span> <span class="attr">OnClick</span><span class="kwrd">="Publish_Click"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">SkinID</span><span class="kwrd">="SavePublish"</span> <span class="attr">RequiredAccess</span><span class="kwrd">="Administer"</span> <span class="attr">text</span><span class="kwrd">="<%$ Resources: EPiServer, button.saveandpublish %>"</span> <span class="attr">ToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, edit.editpanel.tooltipapprove %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"></</span><span class="html">EPiServerUI:ToolButtonContainer</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButtonContainer</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">CssClass</span><span class="kwrd">="epitoolbuttoncontainernoborder"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="Publish"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">OnClick</span><span class="kwrd">="Publish_Click"</span> <span class="attr">SkinID</span><span class="kwrd">="Publish"</span> <span class="attr">RequiredAccess</span><span class="kwrd">="Administer"</span> <span class="attr">text</span><span class="kwrd">="<%$ Resources: EPiServer, button.publish %>"</span> <span class="attr">ToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, button.publish %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="CheckIn"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">OnClick</span><span class="kwrd">="CheckIn_Click"</span> <span class="attr">SkinID</span><span class="kwrd">="CheckIn"</span> <span class="attr">text</span><span class="kwrd">="<%$ Resources: EPiServer, button.checkin %>"</span> <span class="attr">ToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, button.checkin %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"></</span><span class="html">EPiServerUI:ToolButtonContainer</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButtonContainer</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">CssClass</span><span class="kwrd">="epitoolbuttoncontainernoborder"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="Cancel"</span> <span class="attr">OnClick</span><span class="kwrd">="Cancel_Click"</span> <span class="attr">SkinID</span><span class="kwrd">="Cancel"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">text</span><span class="kwrd">="<%$ Resources: EPiServer, button.cancel %>"</span> <span class="attr">ToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, edit.editpanel.tooltipcancel %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"></</span><span class="html">EPiServerUI:ToolButtonContainer</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButtonContainer</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">CssClass</span><span class="kwrd">="epitoolbuttoncontainernoborder epitoolbuttoncontainerright"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="StickyEdit"</span> <span class="attr">OnClick</span><span class="kwrd">="StickyEdit_Click"</span> <span class="attr">OnClientClick</span><span class="kwrd">="EPi.ToolTip.Hide();EPi.PreventDoubleClick(this);"</span> <span class="attr">SkinID</span><span class="kwrd">="StickyEditOff"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">text</span><span class="kwrd">=" "</span> <span class="attr">RichToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, edit.editpanel.tooltipstickyedit %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span> <span class="attr">id</span><span class="kwrd">="UnStickyEdit"</span> <span class="attr">OnClick</span><span class="kwrd">="UnStickyEdit_Click"</span> <span class="attr">OnClientClick</span><span class="kwrd">="EPi.ToolTip.Hide();EPi.PreventDoubleClick(this);"</span> <span class="attr">SkinID</span><span class="kwrd">="StickyEditOn"</span> <span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span> <span class="attr">text</span><span class="kwrd">=" "</span> <span class="attr">RichToolTip</span><span class="kwrd">="<%$ Resources: EPiServer, edit.editpanel.tooltipunstickyedit %>"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"></</span><span class="html">EPiServerUI:ToolButtonContainer</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">asp:Panel</span><span class="kwrd">></span></code></pre>
<p>...and now when anyone who isn’t an Administrator (basically everyone – we only grant Admin access to a very select few) goes to a page, they see the grayed out view seen below and are therefore forced to use our Workflow process.</p>
<p><a href="/link/7af9657217b24e1283a943ad80010ad3.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/fabeab4c246849b2aab24dd1c238ded2.png" width="330" height="42" /></a></p>
<p> </p>
<p>In the next post, I will discuss how we hook the Ready to Publish action in order to send approval notifications when that button is pressed.</p>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>EPiServer Workflow Replacement : Step 4–Rejecting Pages Programmatically/blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-4Rejecting-Pages-Programmatically/2012-04-30T07:37:00.0000000Z<p>This is the fourth in a series of posts about how my company built a replacement workflow platform for EPiServer. Why we chose to do this is explained here: <a title="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/" href="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/">http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/</a></p> <p>I’ll go on record here and say that I don’t love that this is done through the database. This is why SDK’s are made..and this entire process <em>should</em> be able to be done by using the PageProviderBase.SetPageStatus method as described here : <a title="http://sdk.episerver.com/library/cms5/html/M_EPiServer_Core_PageProviderBase_SetPageStatus.htm" href="http://sdk.episerver.com/library/cms5/html/M_EPiServer_Core_PageProviderBase_SetPageStatus.htm">http://sdk.episerver.com/library/cms5/html/M_EPiServer_Core_PageProviderBase_SetPageStatus.htm</a></p> <p>….. but, as I mentioned already, it doesn’t work – so we have to go right into the database. Any time you do this, make sure you know what you’re doing – there’s a good chance you’re mucking with something that isn’t meant to be touched. I take no responsibility for you using this, you’ve been warned<font color="#ff0000">*</font>!</p> <p>(<font color="#ff0000" size="2">*that being said, we’ve been using this in EPiServer CMS 5 for three years and EPiServer CMS 6 for several months, across different environments, and haven’t had any problems</font>)</p> <p>Here’s our RejectPage method. Basically, tblWorkPage seems to have all of the page status information – and all it really  needs is the WorkPageID (pkID) and the PageLinkID (fkPageID) values, and all we do is set ReadyToPublish back to 0, which in EPiServer terms basically means that the page is “Not Ready”.</p> <p> </p> <pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> RejectPage(PageData pageToReject)
{
SqlConnection SQLConnection = <span class="kwrd">new</span> SqlConnection();
<span class="kwrd">string</span> UpdateString = <span class="str">"UPDATE tblWorkPage "</span> +
<span class="str">"SET ReadyToPublish = 0 "</span> +
<span class="str">"WHERE (pkID = "</span> + pageToReject.WorkPageID + <span class="str">" ) "</span> +
<span class="str">"AND (fkPageID = "</span> + pageToReject.PageLink.ID + <span class="str">" )"</span>;
String strConnString = ConfigurationManager.ConnectionStrings[<span class="str">"EPiServerDB"</span>].ConnectionString;
<span class="kwrd">try</span>
{
SQLConnection.ConnectionString = strConnString;
SQLConnection.Open();
SqlCommand mySqlCommand = SQLConnection.CreateCommand();
mySqlCommand.CommandText = UpdateString;
mySqlCommand.ExecuteNonQuery();
}
<span class="kwrd">catch</span> (Exception ex)
{
<span class="kwrd">if</span> (SQLConnection != <span class="kwrd">null</span>)
SQLConnection.Dispose();
ErrorHandler.HandleError(ex);
}
<span class="kwrd">finally</span>
{
SQLConnection.Close();
}
}</code></pre>
<pre class="language-csharp"><code> </code></pre>
<p>We, of course, are an Oracle shop, so most of our instances are actually using the following variant instead: <style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> RejectPageOracle(PageData pageToReject)
{
OracleConnection ORCLConnection = <span class="kwrd">new</span> OracleConnection(strConnString);
<span class="kwrd">string</span> UpdateString = <span class="str">"UPDATE tblWorkPage SET "</span> +
<span class="str">"ReadyToPublish = 0 "</span> +
<span class="str">"WHERE pkID = "</span> + pageToReject.WorkPageID +
<span class="str">"AND fkPageID = "</span> + pageToReject.PageLink.ID;
<span class="kwrd">try</span>
{
ORCLConnection.Open();
OracleCommand oracleCommand = ORCLConnection.CreateCommand();
oracleCommand.CommandText = UpdateString;
oracleCommand.ExecuteNonQuery();
}
<span class="kwrd">catch</span> (Exception ex)
{
ORCLConnection.Close();
ErrorHandler.HandleError(ex);
}
<span class="kwrd">finally</span>
{
ORCLConnection.Close();
}
}</code></pre>
<pre class="language-csharp"><code> </code></pre>
<p>…but you get the idea. In the next post I will discuss how we get around those pesky “Save and Publish” buttons which let people circumvent our new Workflow implementation.</p>EPiServer Workflow Replacement : Step 3–From a high level, how does this work?/blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-3From-a-high-level-how-does-this-work/2012-04-29T19:32:00.0000000Z<p>This is the third in a series of posts about how my company built a replacement workflow platform for EPiServer. Why we chose to do this is explained here: <a href="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/">http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/</a></p> <p>If you’ve been following along, we have now disabled the stock “Workflow” tab and created a GuiPlugin that we’ve replaced it with. But how does all of this work from a conceptual standpoint? </p> <p>Let’s take a step back and pretend we have a new content type that has been created. In theory, the run of the mill author should be able to create this post…but they shouldn’t be the ones who publish it.</p> <p>…or maybe the author and the publisher are the same person, but you want to force the author to get into the practice of reviewing their content before hitting that “Save and Publish” button repeatedly. What purpose, after all, is there to versioning if you have 200 “published” versions which may have incorrect content? </p> <p>From a holistic perspective, publish should only be pressed when content is ready to go live. This system forces that review process to occur.</p> <p>So we have our new content created. Because of how we configure the system in Post #5, the common editor only gets these options available to them:</p> <p><a href="/link/e3faa043a40e4c72bfc1165be4727a75.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/f73a658eb598402bae4da480a5549d41.png" width="293" height="43" /></a></p> <p>For our company, we encourage the “Save and View” option – which brings up the page in preview mode and then gives the user the option to hit the “Ready to Publish” button – which is, again, their only option:</p> <p><a href="/link/ed7e09492efc403dae874c587024e181.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/781388309b7c49c7ae83b193a2edfd24.png" width="162" height="32" /></a></p> <p> </p> <p>When “Ready to Publish” is pressed, an email is sent to all of the approvers in the system for that particular piece of content notifying them that the page is ready to be published and they can look at it (we’ll go into hooking the Ready To Publish action in Post #6).</p> <p><a href="/link/a826aed404d5466890f852d067a36bad.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/bb9700a3d7264276b965d813b3b24848.png" width="485" height="364" /></a></p> <p> </p> <p>We like the idea of having a group of approvers/publishers and requiring only one of them to approve a page before it goes live, so that’s how we set it up..but you can easily modify this to suit your own process.</p> <p>The above email shows a few things – the page title, the person who submitted it, the URL to the actual version of the page that needs to be published (which will prompt a user to log in), the current published version of the page, the action  that the approvers need to take, and the other content approvers for this particular page.</p> <p>Generally, a user should click the “Version URL” link and go right to the page – where they can then select the “Workflow” tab to perform actions on the page itself:</p> <p><a href="/link/a9f661d69d5a4694a9296c88c6c6ad31.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/1623df6a6e594aa5be26d71cda582e09.png" width="417" height="377" /></a></p> <p>As you’ll note, the above page has all of our actions available (since the status is “Ready to Publish”) – the user can publish the version directly, view this version in a browser, compare it with the published version, or reject the page – the latter being the one thing that EPiServer does not support out of the box.</p> <p>If the “Reject” button is pressed, the user is prompted to explain why the version is not satisfactory, the page is rejected (using the methodology explained in Post #4) and the user is notified that they have some changes that need to happen for the post goes live.</p> <p><a href="/link/6d1af4f1ed7d48358ca90205c25668df.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/8a13cf21d93b4faa868f0ee968f676c7.png" width="594" height="218" /></a></p> <p> </p> <p>This is the email that is then triggered to the original content submitter, providing them with some information about the reason behind their content being rejected.</p> <p><a href="/link/e40e9d21e09b47dd8f7edac0259dcbd3.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/afbb997837a54529b8c00bee47b4276e.png" width="415" height="312" /></a></p> <p> </p> <p>If the original content approver logs back in and selects the “Workflow” tab, they’ll see that the page now has the status of “Not Ready” and that they originally marked it as Ready to Publish and it has since been rejected.</p> <p> </p> <p><a href="/link/45df25b990c143e398ee756f96324fa8.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/8351e3f710e342df8ef93ccd98c56b1f.png" width="535" height="455" /></a></p> <p>At this point, the editor should make their corrections, resubmit it to its approvers – who will then get the chance to review it again (and be prompted <em>again</em> before they can hit the Publish button)</p> <p> </p> <p><a href="/link/427b1c4bf85d4d8d946f90ed86b0a9c6.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/e1be1e26467f495d80b63b581b021f47.png" width="539" height="162" /></a></p> <p> </p> <p>..and only after all of this has been implemented can the page be published:</p> <p><a href="/link/455ac212914348598c4a6cf13ebe53d4.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/a913a4c3e7594309b64a8c9587409e0d.png" width="511" height="572" /></a></p> <p> </p> <p>So that’s how this will all come together in the end. In the next post, I’ll discuss how we actually reject pages in both SQL Server and Oracle.</p>EPiServer Workflow Replacement : Step 2–GuiPlugin Creation/blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-2GuiPlugin-Creation/2012-04-28T18:55:00.0000000Z<p>This is the second in a series of posts about how my company built a replacement workflow platform for EPiServer. Why we chose to do this is explained here: <a title="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/" href="http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/">http://world.episerver.com/Blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/</a></p> <p>After the base EPiServer Workflow tab is disabled, the next step in integrating our workflow solution is the creation of a new GuiPlugin to replace it. When done, we will have another tab that looks something like this in Edit Mode:</p> <p><a href="/link/4cab7e5bc6864fcaa781b570350bfe6c.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto; padding-top: 0px" title="image" border="0" alt="image" src="/link/80254b35cce647d4953a5dba0802c3b1.png" width="336" height="319" /></a></p> <p> </p> <p>Alright; so to kick things off, we create a standard Control within our code tree. I like to put these in the /Templates/Advanced/Plugins/ folder within my solutions.</p> <p>In Code Behind, this control should inherit System.Web.UI.UserControl and should be set up as an EPIServer GuiPlugIn like so:</p> <pre class="language-csharp"><code> [GuiPlugIn(
Area = PlugInArea.EditPanel,
DisplayName = <span class="str">"Workflow"</span>,
Url = <span class="str">"~/Templates/Advanced/Plugins/Workflow.ascx"</span>,
RequiredAccess = AccessLevel.Publish)]</code></pre>
<p><style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<p>GuiPlugins are beyond the scope of this discussion, and there are a lot of great articles out there on these. I recommend Ted Nyberg’s blog at <a title="http://labs.episerver.com/en/Blogs/Ted-Nyberg/Dates/2009/2/Adding-a-custom-plugin-button-to-unpublish-a-page-in-EPiServer/" href="http://labs.episerver.com/en/Blogs/Ted-Nyberg/Dates/2009/2/Adding-a-custom-plugin-button-to-unpublish-a-page-in-EPiServer/">http://labs.episerver.com/en/Blogs/Ted-Nyberg/Dates/2009/2/Adding-a-custom-plugin-button-to-unpublish-a-page-in-EPiServer/</a> if you’d like to learn more about creating them.</p>
<p>That being said, essentially my above block of code says that this plugin will appear in the EditPanel region of EPiServer within Edit Mode, the Display Name will be “Workflow” (just like the old tab we disabled in Step 1), the URL will be this control we are creating, and users must have Publish access in order to view this tab.</p>
<p>In our Workflow.ascx file on the front end, we begin building out our top area (Quick Version Statistics). </p>
<pre class="language-csharp"><code><span class="kwrd"><</span><span class="html">div</span> <span class="attr">style</span><span class="kwrd">="padding-left:20px; padding-top:20px;"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">h2</span><span class="kwrd">></span>Quick Version Statistics:<span class="kwrd"></</span><span class="html">h2</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">table</span> <span class="attr">style</span><span class="kwrd">="border: 1px solid #000; height: 136px;"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">tr</span><span class="kwrd">><</span><span class="html">td</span> <span class="attr">width</span><span class="kwrd">="80px"</span><span class="kwrd">></span>Version Title: <span class="kwrd"></</span><span class="html">td</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span><span class="asp"><%</span>= (Page <span class="kwrd">as</span> EPiServer.PageBase).CurrentPage.PageName <span class="asp">%></span><span class="kwrd"></</span><span class="html">td</span><span class="kwrd">></</span><span class="html">tr</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">tr</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span>Version Number: <span class="kwrd"></</span><span class="html">td</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span><span class="asp"><%</span>= (Page <span class="kwrd">as</span> EPiServer.PageBase).CurrentPage.WorkPageID <span class="asp">%></span><span class="kwrd"></</span><span class="html">td</span><span class="kwrd">></</span><span class="html">tr</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">tr</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span>Version Status: <span class="kwrd"></</span><span class="html">td</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span><span class="asp"><%</span>= GetVersionStatus <span class="asp">%></span><span class="kwrd"></</span><span class="html">td</span><span class="kwrd">></</span><span class="html">tr</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">tr</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span>Saved By: <span class="kwrd"></</span><span class="html">td</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span><span class="asp"><%</span>= UserProfileTools.GetFullName((Page <span class="kwrd">as</span> EPiServer.PageBase).CurrentPage.ChangedBy) <span class="asp">%></span><span class="kwrd"></</span><span class="html">td</span><span class="kwrd">></</span><span class="html">tr</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">tr</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span>Page Saved On: <span class="kwrd"></</span><span class="html">td</span><span class="kwrd">><</span><span class="html">td</span><span class="kwrd">></span><span class="asp"><%</span>= (Page <span class="kwrd">as</span> EPiServer.PageBase).CurrentPage.Saved <span class="asp">%></span><span class="kwrd"></</span><span class="html">td</span><span class="kwrd">></</span><span class="html">tr</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">table</span><span class="kwrd">></span></code></pre>
<pre class="language-csharp"><code><span class="kwrd"><font color="#000000"></font></span> </code></pre>
<p>At this point it is worth noting a few things..first of all, I’m not a front end developer; so you will see some reliance in my markup on things like inline styles and tables. Someone could take this stuff much further and make it look much prettier..but for our purposes, this is enough. Secondly; we have also integrated some other items like our custom User Profile Tools, which essentially just adds first names, last names and pictures to user profiles, as well as our logging tool platform – which basically grabs all actions in the system and logs them in an external table. These are also out of scope for this blog series, so I’d just ignore that stuff if I were you..</p>
<p>I was a big fan of .NET panels when this was originally written, and this application uses them in abundance. Below the Quick version statistics, we add a Rejection Panel to our control, hidden by default, like so:</p>
<pre class="language-csharp"><code><span class="kwrd"><</span><span class="html">br</span> <span class="kwrd">/><</span><span class="html">br</span> <span class="kwrd">/><</span><span class="html">br</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">asp:Panel</span> <span class="attr">ID</span><span class="kwrd">="RejectionPanel"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Visible</span><span class="kwrd">="False"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">h2</span><span class="kwrd">></span>Enter Reason for Rejection:<span class="kwrd"></</span><span class="html">h2</span><span class="kwrd">><</span><span class="html">br</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">asp:TextBox</span> <span class="attr">ID</span><span class="kwrd">="txtRejectReason"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">TextMode</span><span class="kwrd">="MultiLine"</span> <span class="attr">Rows</span><span class="kwrd">="5"</span> <span class="attr">Width</span><span class="kwrd">="80%"</span><span class="kwrd">></</span><span class="html">asp:TextBox</span><span class="kwrd">><</span><span class="html">br</span> <span class="kwrd">/><</span><span class="html">br</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">br</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span>
<span class="attr">ID</span><span class="kwrd">="btnReject"</span>
<span class="attr">OnClick</span><span class="kwrd">="Reject"</span>
<span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span>
<span class="attr">Text</span><span class="kwrd">="Reject Version"</span>
<span class="attr">SkinID</span><span class="kwrd">="Warning"</span>
<span class="attr">ToolTip</span><span class="kwrd">="Rejects this version of the page"</span>
<span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"></</span><span class="html">asp:Panel</span><span class="kwrd">></span> </code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p> </p>
<p>You’ll notice this is the first use of EPiServerUI:ToolButton. This needs to be enabled in web.config within the <controls> block around line 378 (mine is just above the EPiServer.WebControls control)</p>
<pre class="language-csharp"><code> <add tagPrefix=<span class="str">"EPiServerUI"</span> <span class="kwrd">namespace</span>=<span class="str">"EPiServer.UI.WebControls"</span> assembly=<span class="str">"EPiServer.UI"</span> /></code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p> </p>
<p>Next we add our Standard Panel, which contains most of the Workflow actions that can be performed on this particular version, like so:</p>
<pre class="language-csharp"><code><span class="kwrd"><</span><span class="html">asp:Panel</span> <span class="attr">ID</span><span class="kwrd">="StandardPanel"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">h2</span><span class="kwrd">></span>Workflow Actions Available:<span class="kwrd"></</span><span class="html">h2</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButtonContainer</span> <span class="attr">ID</span><span class="kwrd">="ToolButtons"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span>
<span class="attr">ID</span><span class="kwrd">="btnDisplayReject"</span>
<span class="attr">OnClick</span><span class="kwrd">="DisplayRejectionPanel"</span>
<span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span>
<span class="attr">Text</span><span class="kwrd">="Reject Version"</span>
<span class="attr">SkinID</span><span class="kwrd">="Warning"</span>
<span class="attr">ToolTip</span><span class="kwrd">="Rejects this version of the page"</span>
<span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span>
<span class="attr">ID</span><span class="kwrd">="btnReadyToPublish"</span>
<span class="attr">OnClick</span><span class="kwrd">="ReadyToPublish"</span>
<span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span>
<span class="attr">Text</span><span class="kwrd">="Ready to Publish"</span>
<span class="attr">SkinID</span><span class="kwrd">="Check"</span>
<span class="attr">ToolTip</span><span class="kwrd">="Mark this version Ready to Publish"</span>
<span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">EPiServerUI:ToolButton</span>
<span class="attr">ID</span><span class="kwrd">="btnPublish"</span>
<span class="attr">OnClick</span><span class="kwrd">="Publish"</span>
<span class="attr">OnClientClick</span><span class="kwrd">="return confirm('Do you approve of ALL changes on this page?');"</span>
<span class="attr">DisablePageLeaveCheck</span><span class="kwrd">="true"</span>
<span class="attr">Text</span><span class="kwrd">="Publish Version"</span>
<span class="attr">SkinID</span><span class="kwrd">="Publish"</span>
<span class="attr">ToolTip</span><span class="kwrd">="Publishes this version of the page"</span>
<span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">style</span> <span class="attr">type</span><span class="kwrd">="text/css"</span> <span class="attr">media</span><span class="kwrd">="screen"</span><span class="kwrd">></span>
span.VersionButton { font-size:11px; vertical-align:top; text-decoration:none; }
<span class="kwrd"></</span><span class="html">style</span><span class="kwrd">></span>
<span class="rem"><!--[if lt IE 8]></span>
<span class="rem"> <style type="text/css" media="screen"></span>
<span class="rem"> span.VersionButton { vertical-align:middle; }</span>
<span class="rem"> </style></span>
<span class="rem"> <![endif]--></span>
<span class="asp"><%</span><span class="kwrd">if</span> (!isPublished) { <span class="asp">%></span><span class="kwrd"><</span><span class="html">span</span> <span class="attr">class</span><span class="kwrd">="epitoolbutton"</span> <span class="attr">Target</span><span class="kwrd">=""</span><span class="kwrd">><</span><span class="html">a</span> <span class="attr">href</span><span class="kwrd">="<%= (GetVersionUrl) %>"</span> <span class="attr">target</span><span class="kwrd">="_blank"</span> <span class="attr">style</span><span class="kwrd">="text-decoration:none; color: #000;"</span><span class="kwrd">><</span><span class="html">img</span> <span class="attr">src</span><span class="kwrd">="/App_Themes/Default/Images/Tools/ViewMode.gif"</span> <span class="attr">alt</span><span class="kwrd">=""</span> <span class="kwrd">/><</span><span class="html">span</span> <span class="attr">class</span><span class="kwrd">="VersionButton"</span><span class="kwrd">></span>View Version<span class="kwrd"></</span><span class="html">span</span><span class="kwrd">></</span><span class="html">a</span><span class="kwrd">></</span><span class="html">span</span><span class="kwrd">></span> <span class="asp"><%</span> } <span class="asp">%></span>
<span class="kwrd"><</span><span class="html">span</span> <span class="attr">class</span><span class="kwrd">="epitoolbutton"</span> <span class="attr">Target</span><span class="kwrd">=""</span><span class="kwrd">><</span><span class="html">a</span> <span class="attr">href</span><span class="kwrd">="<%= (GetPublishedUrl) %>"</span> <span class="attr">target</span><span class="kwrd">="_blank"</span> <span class="attr">style</span><span class="kwrd">="text-decoration:none; color: #000;"</span><span class="kwrd">><</span><span class="html">img</span> <span class="attr">src</span><span class="kwrd">="/App_Themes/Default/Images/Tools/ViewMode.gif"</span> <span class="attr">alt</span><span class="kwrd">=""</span> <span class="kwrd">/><</span><span class="html">span</span> <span class="attr">class</span><span class="kwrd">="VersionButton"</span><span class="kwrd">></span>View Published Version<span class="kwrd"></</span><span class="html">span</span><span class="kwrd">></</span><span class="html">a</span><span class="kwrd">></</span><span class="html">span</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">br</span> <span class="kwrd">/><</span><span class="html">br</span> <span class="kwrd">/><</span><span class="html">br</span> <span class="kwrd">/></span>
<span class="kwrd"><</span><span class="html">asp:Label</span> <span class="attr">ID</span><span class="kwrd">="lblHistory"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">=""</span><span class="kwrd">></</span><span class="html">asp:Label</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">EPiServerUI:ToolButtonContainer</span><span class="kwrd">></span></code></pre>
<p><style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<p>IE8 has a stupid bug that prevents the buttons from displaying properly, hence the hack in the middle of the markup there. This panel is visible by default, and contains buttons for Publishing the page, viewing this version of the page, rejecting the page, and marking the page Ready to Publish.</p>
<p>On to the code behind! First we override OnLoad and grab a writable clone, which we call PageToEdit and make public within the Workflow class. We grab a writeable clone here so EPiServer allows us to edit it (<a title="http://sdk.episerver.com/library/cms5/html/M_EPiServer_Core_PageData_CreateWritableClone.htm" href="http://sdk.episerver.com/library/cms5/html/M_EPiServer_Core_PageData_CreateWritableClone.htm">http://sdk.episerver.com/library/cms5/html/M_EPiServer_Core_PageData_CreateWritableClone.htm</a>)</p>
<pre class="language-csharp"><code> PageData pageToEdit;
<span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnLoad(EventArgs e)
{
<span class="kwrd">base</span>.OnLoad(e);
pageToEdit = (Page <span class="kwrd">as</span> PageBase).CurrentPage.CreateWritableClone();
lblHistory.Text = <span class="str">"<h2>Version History:</h2>"</span> + LoggingTools.GetVersionHistory(pageToEdit);
setButtonVisibility();
RejectionPanel.Visible = <span class="kwrd">false</span>;
StandardPanel.Visible = <span class="kwrd">true</span>;
}</code></pre>
<p><style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<p>We then add our setButtonVisibility method, which displays buttons based on the status of the page as well as our isPublished and isCheckedIn properties:</p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> setButtonVisibility()
{
btnReject.Visible = !isPublished;
btnPublish.Visible = (!isPublished & isCheckedIn);
btnDisplayReject.Visible = (!isPublished & isCheckedIn);
btnReadyToPublish.Visible = (!isPublished & !isCheckedIn);
}</code></pre>
<p><style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">bool</span> isPublished
{
get
{
<span class="kwrd">return</span> pageToEdit.CheckPublishedStatus(PagePublishedStatus.Published);
}
}
<span class="kwrd">protected</span> <span class="kwrd">bool</span> isCheckedIn
{
get
{
<span class="kwrd">if</span> (pageToEdit.Status == VersionStatus.CheckedIn)
{
<span class="kwrd">return</span> <span class="kwrd">true</span>;
}
<span class="kwrd">else</span>
{
<span class="kwrd">return</span> <span class="kwrd">false</span>;
}
}
}</code></pre>
<pre class="language-csharp"><code> </code></pre>
<p>…and then our methods for displaying the proper URL’s for our buttons, GetVersionUrl and GetPublishedUrl, both of which are dependent on an appsettings value called “BaseUrl” which may not be applicable in your environment, but is used in a lot of our application.</p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">string</span> GetVersionUrl
{
get
{
String PageVersionUrl = ConfigurationManager.AppSettings[<span class="str">"BaseUrl"</span>] + pageToEdit.LinkURL;
PageVersionUrl = PageVersionUrl.Replace(<span class="str">"&epslanguage"</span>, <span class="str">"_"</span> + pageToEdit.WorkPageID + <span class="str">"&idkeep=True&epslanguage"</span>);
<span class="kwrd">return</span> PageVersionUrl;
}
}
<span class="kwrd">protected</span> <span class="kwrd">string</span> GetPublishedUrl
{
get
{
String PagePublishedUrl = ConfigurationManager.AppSettings[<span class="str">"BaseUrl"</span>] + pageToEdit.LinkURL;
<span class="kwrd">return</span> PagePublishedUrl;
}
}</code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p> </p>
<p>…we then implement the GetVersionStatus method, which, admittedly, uses a lot of case statements and could be cleaned up – but basically just looks at the PageStatus and returns some HTML with information based on that.</p>
<p> </p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">string</span> GetVersionStatus
{
get
{
VersionStatus PageStatus = (Page <span class="kwrd">as</span> PageBase).CurrentPage.Status;
<span class="kwrd">if</span> (PageStatus == VersionStatus.CheckedIn)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #f96;\">Ready to Publish</span>"</span>;
}
<span class="kwrd">else</span> <span class="kwrd">if</span> (PageStatus == VersionStatus.CheckedOut)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #f00;\">Not Ready</span>"</span>;
}
<span class="kwrd">else</span> <span class="kwrd">if</span> (PageStatus == VersionStatus.DelayedPublish)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #3c3;\">Delayed Publish</span>"</span>;
}
<span class="kwrd">else</span> <span class="kwrd">if</span> (PageStatus == VersionStatus.NotCreated)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #0f0;\">Not Created</span>"</span>;
}
<span class="kwrd">else</span> <span class="kwrd">if</span> (PageStatus == VersionStatus.PreviouslyPublished)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #06c;\">Previously Published</span>"</span>;
}
<span class="kwrd">else</span> <span class="kwrd">if</span> (PageStatus == VersionStatus.Published)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #3c3;\">Published</span>"</span>;
}
<span class="kwrd">else</span> <span class="kwrd">if</span> (PageStatus == VersionStatus.Rejected)
{
<span class="kwrd">return</span> <span class="str">"<span style=\"color: #f00;\">Rejected</span>"</span>;
}
<span class="kwrd">else</span>
{
<span class="kwrd">return</span> <span class="str">"Unknown Status"</span>;
}
}
}</code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p> </p>
<p>On to rejections! This first method is actually just to display the Rejection panel itself and is pretty self explanatory. The second performs the actual rejection through our PageProviderTools (discussed in post #4) and sends out email notification saying that the item has been rejected.</p>
<p> </p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> DisplayRejectionPanel(<span class="kwrd">object</span> sender, EventArgs e)
{
RejectionPanel.Visible = <span class="kwrd">true</span>;
StandardPanel.Visible = <span class="kwrd">false</span>;
}
<span class="kwrd">protected</span> <span class="kwrd">void</span> Reject(<span class="kwrd">object</span> sender, EventArgs e)
{
String RejectionReason = txtRejectReason.Text;
String PageVersionUrl = ConfigurationManager.AppSettings[<span class="str">"BaseUrl"</span>] + pageToEdit.LinkURL;
PageVersionUrl = PageVersionUrl.Replace(<span class="str">"&epslanguage"</span>, <span class="str">"_"</span> + pageToEdit.WorkPageID + <span class="str">"&idkeep=True&epslanguage"</span>);
<span class="rem">//Get Email Address of Person who submitted page for review</span>
System.Web.Security.MembershipUser SubmittedBy = System.Web.Security.Membership.GetUser(pageToEdit.ChangedBy);
PageProviderTools PPT = <span class="kwrd">new</span> PageProviderTools();
PPT.SetThisPageStatus((Page <span class="kwrd">as</span> PageBase).CurrentPage, VersionStatus.Rejected);
<span class="rem">//Send rejection email to person who submitted it</span>
String EmailBody = <span class="str">"<h2>EPiServer System Notification: Content Rejected</h2><table style=\"font-family: Arial; font-size: 12px; \"><tr><td><b>Title:</b></td><td>"</span> + pageToEdit.PageName + <span class="str">"</td></tr><tr><td><b>Rejector:</b></td><td>"</span> + UserProfileTools.GetFullName(pageToEdit.ChangedBy) + <span class="str">"</td></tr><tr><td><b>Version URL:</b></td><td>"</span> + PageVersionUrl + <span class="str">"</td></tr><tr><td><b>Published URL:</b></td><td>"</span> + ConfigurationManager.AppSettings[<span class="str">"BaseUrl"</span>] + pageToEdit.LinkURL + <span class="str">"</td></tr><tr><td><b>Description:</b></td><td>This page has been rejected for publishing. Specific details provided below.</td></tr><tr><td><b>Rejection Reason:</b></td><td>"</span> + RejectionReason + <span class="str">"</td></tr></table>"</span>;
List<String> EmailList = <span class="kwrd">new</span> List<String>();
EmailList.Add(SubmittedBy.Email);
<span class="kwrd">if</span> (EmailList.Count() > 0)
EmailTools.SendTemplatedEmail(<span class="str">"EPiServer Page Rejection - <a href="http://www.mysite.com">www.mysite.com</a>"</span>, EmailBody, EmailList, <span class="str">"EPiServer@episerver.com"</span>, <span class="str">"EPiServer"</span>);
LoggingTools.LogVersionStatus(pageToEdit, <span class="str">"Rejected"</span>, <span class="str">"Rejection Comments: "</span> + RejectionReason.ToString(), PrincipalInfo.Current);
<span class="rem">//Reset the panel display</span>
RejectionPanel.Visible = <span class="kwrd">false</span>;
StandardPanel.Visible = <span class="kwrd">true</span>;
<span class="rem">//Reset the rejection reason</span>
txtRejectReason.Text = <span class="str">""</span>;
<span class="rem">//Reload the current tab ("Workflow")</span>
reload();
}</code></pre>
<pre class="language-csharp"><code> </code></pre>
<p>..then, we add in methods for all of the other buttons in our arsenal.</p>
<p><style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> Publish(<span class="kwrd">object</span> sender, EventArgs e)
{
<span class="rem">//Publish the modified page</span>
DataFactory.Instance.Save(pageToEdit, SaveAction.Publish);
<span class="rem">//Reload the current tab ("Workflow")</span>
reload();
}
<span class="kwrd">protected</span> <span class="kwrd">void</span> ReadyToPublish(<span class="kwrd">object</span> sender, EventArgs e)
{
<span class="rem">//Publish the modified page</span>
DataFactory.Instance.Save(pageToEdit, SaveAction.CheckIn);
<span class="rem">//Reload the current tab ("Workflow")</span>
reload();
}</code></pre>
<pre class="language-csharp"><code> </code></pre>
<p>Lastly, we add in FindControl and reload – the latter is used to update the tab with the correct information once it has been submitted, the former is used to find the appropriate TabStrip.</p>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> reload()
{
<span class="rem">//Find the tab strip</span>
TabStrip actionTabStrip = <span class="kwrd">this</span>.FindControl<TabStrip>(Page, <span class="str">"actionTab"</span>);
<span class="rem">//Compose a URL for the currently selected tab ("Workflow")</span>
String url = <span class="kwrd">string</span>.Format(<span class="str">"EditPanel.aspx?{0}={1}"</span>,
actionTabStrip.SelectedTabQueryString,
actionTabStrip.SelectedTab);
<span class="rem">//Append ID or it will default to home page on reload</span>
url = url + <span class="str">"&id="</span> + pageToEdit.PageLink.ID + <span class="str">"_"</span> + pageToEdit.WorkPageID;
lblHistory.Text = <span class="str">"<h2>Version History:</h2>"</span> + LoggingTools.GetVersionHistory(pageToEdit);
<span class="rem">//Redirect to the URL to reload the page</span>
Response.Redirect(url);
}
<span class="kwrd">protected</span> T FindControl<T>(Control control, <span class="kwrd">string</span> id) <span class="kwrd">where</span> T : Control
{
T controlTest = control <span class="kwrd">as</span> T;
<span class="kwrd">if</span> (<span class="kwrd">null</span> != controlTest && (<span class="kwrd">null</span> == id || controlTest.ID.Equals(id)))
<span class="kwrd">return</span> controlTest;
<span class="kwrd">foreach</span> (Control c <span class="kwrd">in</span> control.Controls)
{
controlTest = FindControl<T>(c, id);
<span class="kwrd">if</span> (<span class="kwrd">null</span> != controlTest)
<span class="kwrd">return</span> controlTest;
}
<span class="kwrd">return</span> <span class="kwrd">null</span>;
}</code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p>In the next post, I’ll describe how all of this comes together from a conceptual level – as this is a lot of code and doesn’t quite put everything into proper context!</p>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>EPiServer Workflow Replacement : Step 1–Explanation and Disabling Edit Tab/blogs/Hans/Dates/2012/4/EPiServer-Workflow-Replacement--Step-1Explanation-and-Disabling-Edit-Tab/2012-04-27T16:36:30.0000000Z<p>This is the first of a few different posts I’d like to write about EPiServer Workflow, its limitations, and how we’ve provided a custom workaround that seems to work pretty well for our company.</p> <p>For all intents and purposes, EPiServer workflow functions fine out of the box..but it has some limitations that prevent us from using it here internally, namely, pages cannot be rejected.</p> <p>Now I know the official party line is that the provided workflows are just a template for how workflow can be set up, and I don’t want to spend too much time pointing out a flaw in the application, but there is no way to set a page to rejected. It just doesn’t work. </p> <p>Try it yourself if you do not believe me. <img style="border-bottom-style: none; border-left-style: none; border-top-style: none; border-right-style: none" class="wlEmoticon wlEmoticon-openmouthedsmile" alt="Open-mouthed smile" src="/link/7f1ed2d05087416ea4a895a817c6d479.png" /> The Enumeration for VersionStatus in CMS 6 (<a title="http://sdk.episerver.com/library/cms6/html/T_EPiServer_Core_VersionStatus.htm" href="http://sdk.episerver.com/library/cms6/html/T_EPiServer_Core_VersionStatus.htm">http://sdk.episerver.com/library/cms6/html/T_EPiServer_Core_VersionStatus.htm</a>) remains the same as it does for CMS 5 – clearly showing a VersionStatus.Rejected…</p> <p>…so by definition we should be able to do this:</p> <p>          PageProviderTools PPT = new PageProviderTools(); <br />          PPT.SetThisPageStatus(pageToEditNow, VersionStatus.Rejected);</p> <p>-----------------------</p> <p>public class PageProviderTools : LocalPageProvider <br />{ <br />    public void SetThisPageStatus(PageData pageToEdit, VersionStatus versionStatus) <br />    { <br />        SetPageStatus(pageToEdit, versionStatus); <br />    } <br />} <br /></p> <p> </p> <p>….but it doesn’t work. I’ve even gone into the database and set the table manually to “rejected” – which only ends up displaying the text label “Comments” on a page’s version status; leading me to believe that although the SDK provides for a VersionStatus.Rejected, it was never actually implemented.</p> <p>If we have editors who request that an item be published and the approvers determine that the item is not ready to be published, there is no method for the approver to reject the requested version. This leads to a lot of “Ready to Publish” versions sitting in the CMS, which leaves the application cluttered and difficult to determine which version is actually the correct version. This might not be a big deal in smaller installations, but when you have 40 global content authors editing 12 different language versions of the same site…it quickly becomes a hassle.</p> <p>So…a  new method was needed, and this series of posts will explain how we did it.</p> <p>----------------------------------</p> <p>The first thing we do when setting up workflow for our environment is to disable the workflow tab provided by EPiServer. We do this by going to Admin Mode, selecting the “Config” tab, and then selecting “Plug-in Manager”</p> <p> </p> <p><a href="/link/dc2d91f62844416e93527f5edaa365ab.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/8a1173126cca4d30b102658c6e820760.png" width="180" height="299" /></a></p> <p> </p> <p>Then we select “EPiServer User Interface” from the list of options available, </p> <p><a href="/link/59ca597a23b747479bd71ba1a0731369.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/1fccb57a5106477e90bdc5e83e17d5e3.png" width="203" height="45" /></a></p> <p> </p> <p>select the “Overview” tab,</p> <p> </p> <p align="center"><a href="/link/ce023942644c4dcd9259cac2ba00cf3b.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/ff2919d192fc4ba694b764142e6d896b.png" width="226" height="54" /></a></p> <p> </p> <p>…and uncheck the “Workflow (EditPanel)” option</p> <p><a href="/link/9ad82e6eba7d4f6fb7d5b4e3b3539d98.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/244f4cd25e4e419c82c33bdd5d561027.png" width="244" height="34" /></a></p> <p> </p> <p>This prevents the default EPiServer workflow tab from appearing in Edit Mode. In the next post, we’ll write a GuiPlugin that replaces this tab with our own “Workflow” implementation….</p>Batch Update EPiServer Page Properties after Creation/blogs/Hans/Dates/2012/1/Batch-Update-EPiServer-Page-Properties-after-Creation/2012-01-14T17:32:37.0000000Z<p>I’ll start this with a warning: this code can really mess up your site. I just created it to get something done quickly, and I’m only sharing it in case you’re in a similar situation. Be very careful with the values you enter, and as always, test, test, and retest before running this in a production environment!</p> <p>Anyway… I ran into a situation today where I had to update a PropertyString value on 297 existing pages. Instead of setting this manually, I whipped up the following GuiPlugin. It takes three values : Page Type, Property Name, and New Value. This only works on String properties currently.</p> <p>First – create a new GUI Plugin. In the code behind, add the following:</p> <p> </p> <div class="csharpcode"> <pre class="language-csharp"><code> [GuiPlugIn(DisplayName = <span class="str">"Batch Update"</span>, Area = PlugInArea.AdminMenu, <br />Url = <span class="str">"~/Templates/Advanced/Plugins/UpdateProperties.aspx"</span>, Description = <br /><span class="str">"This plugin batch updates a single page property to a specific value."</span>)]</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> UpdateProperties : System.Web.UI.Page</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnLoad(EventArgs e)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">base</span>.OnLoad(e);</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (!PrincipalInfo.HasAdminAccess)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> pnlStart.Visible = <span class="kwrd">false</span>;</code></pre>
<pre class="language-csharp"><code> pnlComplete.Visible = <span class="kwrd">true</span>;</code></pre>
<pre class="language-csharp"><code> lblComplete.Text = <span class="str">"Access denied."</span>;</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">public</span> String UpdateStringProperties(String PageType, String PropertyName, <br />String NewValue)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">int</span> count = 0;</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">try</span></code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> PageData WriteableClone = <span class="kwrd">null</span>;</code></pre>
<pre class="language-csharp"><code> IEnumerable<PageData> AllPagesOfType = <br />PageHelpers.GetPagesByTypeRecursive(PageType, <br />DataFactory.Instance.GetPage(PageReference.StartPage));</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">foreach</span> (PageData ThisPage <span class="kwrd">in</span> AllPagesOfType)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> WriteableClone = ThisPage.CreateWritableClone();</code></pre>
<pre class="language-csharp"><code> WriteableClone[PropertyName] = NewValue;</code></pre>
<pre class="language-csharp"><code> DataFactory.Instance.Save(WriteableClone, <br />EPiServer.DataAccess.SaveAction.Publish | <br />EPiServer.DataAccess.SaveAction.ForceCurrentVersion, <br />EPiServer.Security.AccessLevel.FullAccess);</code></pre>
<pre class="language-csharp"><code> count++;</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">catch</span> (System.Exception ex)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">return</span> ex.ToString();</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">return</span> <span class="str">"Process successfully completed for "</span> +<br /> count.ToString() + <span class="str">" pages."</span>;</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">void</span> btnSubmit_Click(<span class="kwrd">object</span> sender, EventArgs e)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> String PageType = <span class="kwrd">this</span>.txtPageType.Text;</code></pre>
<pre class="language-csharp"><code> String PropertyName = <span class="kwrd">this</span>.txtPropertyName.Text;</code></pre>
<pre class="language-csharp"><code> String NewValue = <span class="kwrd">this</span>.txtNewValue.Text;</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (String.IsNullOrEmpty(PageType) <br />|| String.IsNullOrEmpty(PropertyName) || <br />String.IsNullOrEmpty(NewValue))</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> lblValidation.Text = <span class="str">"All fields must be entered"</span>;</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">else</span></code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> pnlStart.Visible = <span class="kwrd">false</span>;</code></pre>
<pre class="language-csharp"><code> pnlComplete.Visible = <span class="kwrd">true</span>;</code></pre>
<pre class="language-csharp"><code> lblComplete.Text = UpdateStringProperties(PageType, <br />PropertyName, NewValue);</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
</div>
<p> </p>
<p>Note: The above references a function called GetPagesByTypeRecursive, which looks like the following:</p>
<pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">static</span> IEnumerable<PageData> GetPagesByTypeRecursive(String <br />PageTypeName, PageData StartPage)
{
List<PageData> AllPages = <span class="kwrd">new</span> List<PageData>();
PageDataCollection ThisCollection = <br />DataFactory.Instance.GetChildren(StartPage.PageLink);
<span class="kwrd">foreach</span> (PageData ThisPage <span class="kwrd">in</span> ThisCollection)
{
<span class="kwrd">if</span> (HasChildren(ThisPage))
{
IEnumerable<PageData> ThisList = <br />GetPagesByTypeRecursive(PageTypeName, ThisPage);
<span class="kwrd">foreach</span> (PageData SubPage <span class="kwrd">in</span> ThisList)
{
<span class="kwrd">if</span> (SubPage.PageTypeName == PageTypeName) AllPages.Add(SubPage);
}
}
<span class="kwrd">if</span> (ThisPage.PageTypeName == PageTypeName) AllPages.Add(ThisPage);
}
<span class="kwrd">return</span> AllPages;
}</code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p><style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style></p>
<p>Your aspx page can look like the following:</p>
<pre class="language-csharp"><code><span class="asp"><%@ Page Language="C#" AutoEventWireup="true" <br />CodeBehind="UpdateProperties.aspx.cs" <br />Inherits="MyProject.Templates.Advanced.Plugins.UpdateProperties" %></span>
<span class="kwrd"><!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">"-//W3C//DTD XHTML 1.0 Transitional//EN"</span> <br /><span class="kwrd">"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">="http://www.w3.org/1999/xhtml"</span> <span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">head</span> <span class="attr">id</span><span class="kwrd">="Head1"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">title</span><span class="kwrd">></span>Update Properties Plugin<span class="kwrd"></</span><span class="html">title</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">head</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">body</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">form</span> <span class="attr">id</span><span class="kwrd">="form1"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">asp:Panel</span> <span class="attr">ID</span><span class="kwrd">="pnlStart"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">h1</span><span class="kwrd">></span>Update Properties Plugin<span class="kwrd"></</span><span class="html">h1</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">fieldset</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">p</span><span class="kwrd">></span>Page Type: <span class="kwrd"><</span><span class="html">asp:TextBox</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">ID</span><span class="kwrd">="txtPageType"</span> <span class="kwrd">/></</span><span class="html">p</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">p</span><span class="kwrd">></span>Property Name: <span class="kwrd"><</span><span class="html">asp:TextBox</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">ID</span><span class="kwrd">="txtPropertyName"</span> <span class="kwrd">/></</span><span class="html">p</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">p</span><span class="kwrd">></span>New Value: <span class="kwrd"><</span><span class="html">asp:TextBox</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">ID</span><span class="kwrd">="txtNewValue"</span> <span class="kwrd">/></</span><span class="html">p</span><span class="kwrd">></span>
<<span class="html">p</span><span class="kwrd">><</span><span class="html">asp:Button</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">ID</span><span class="kwrd">="btnSubmit"</span> <span class="attr">CssClass</span><span class="kwrd">="submit"</span> <br /><span class="attr">Text</span><span class="kwrd">="Submit"</span> <span class="attr">OnClick</span><span class="kwrd">="btnSubmit_Click"</span> <span class="kwrd">/></</span><span class="html">p</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">asp:Label</span> <span class="attr">ID</span><span class="kwrd">="lblValidation"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">=""</span><span class="kwrd">></</span><span class="html">asp:Label</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">fieldset</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">asp:Panel</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">asp:Panel</span> <span class="attr">ID</span><span class="kwrd">="pnlComplete"</span> <span class="attr">Visible</span><span class="kwrd">="false"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span>
<span class="kwrd"><</span><span class="html">asp:Label</span> <span class="attr">ID</span><span class="kwrd">="lblComplete"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">=""</span><span class="kwrd">></</span><span class="html">asp:Label</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">asp:Panel</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">form</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">body</span><span class="kwrd">></span>
<span class="kwrd"></</span><span class="html">html</span><span class="kwrd">></span></code></pre>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p>This will give you a nice little Plugin under Tools in Admin:</p>
<p><a href="/link/7921ff17598f40b1b68304d990460dd4.png"><img style="background-image: none; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/f36cd01e734947ce9975ab9cc344870b.png" width="244" height="168" /></a></p>
<p> </p>
<p>Which allows you to set a Page Type, Property Name and New Property Value – all as String – and, once submitted, will iterate through all pages in your site (from StartPage down) and set that property value. In my test instance, 297 pages took about 25 seconds to run.</p>
<p><a href="/link/98e3b258289f45a19977114e820b2c36.png"><img style="background-image: none; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="/link/492b2e83f73849cf832d460c1c2eee1b.png" width="244" height="158" /></a></p>
<p> </p>
<p>This could easily be refined and extended, but for about 15 minutes worth of work I now have an (admittedly dangerous) easy, repeatable process that allows me to update page values after creation.</p>CreatedBy Not Set When Jobs run on a Schedule – Oracle Error/blogs/Hans/Dates/2010/6/CreatedBy-Not-Set-When-Jobs-run-on-a-Schedule--Oracle-Error/2010-06-30T17:53:37.0000000Z<p>I recently stumbled across a bug in EPiServer that relates directly to Oracle. It’s somewhat obscure, so I figured it would be a good idea to post how we dealt with it here…</p> <p>When a page is created through the SDK’s DataFactory.Instance.Save method, EPiServer automatically sets CreatedBy to the person who is logged in and executing the code. So for manual page creation through the SDK, the following works just fine…</p> <pre class="language-csharp"><code>PageData myPage = EPiServer.DataFactory.Instance.GetDefaultPageData(
parent, <span class="str">"Standard page"</span>);
myPage.PageName = <span class="str">"My new page"</span>;
myPage.Property[<span class="str">"MainBody"</span>].Value = <span class="str">"<p>This is produced programmatically.</p>"</span>;
myPage.VisibleInMenu = <span class="kwrd">false</span>;
myPage.StartPublish = DateTime.Now.AddMinutes(-1);
DataFactory.Instance.Save(myPage, EPiServer.DataAccess.SaveAction.CheckIn, AccessLevel.NoAccess);</code></pre>
<p>But when this is set up to be run in a job that is scheduled, the CreatedBy is set to NULL. For the vast majority of sites that run EPiServer on SQL Server, this isn’t an issue. But those sites that run on Oracle will find that, upon logging into their CMS, the application throws a lovely “Unable to Cast object of type ‘System.DBNull’ to type ‘System.String’.” error.</p>
<p> </p>
<p><a href="/link/4ff197025fe14460836e5a3787642eab.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="/link/1987eb333dc24ee3898a2f1df4613419.png" width="548" height="244" /></a> </p>
<p> </p>
<p>What’s happening here is that EPiServer is calling several FindPageActivities methods, and looking at the Activities associated with a particular user name. In this case, it sees that a page needs to be displayed, but it has NULL for CreatedBy. In SQL Server, an empty string is displayed. In Oracle, an exception is thrown.</p>
<p>This is a bug, and will likely be addressed by EPiServer at some later point.. but in the interim, a workaround needs to be found for those who might be experiencing this issue.</p>
<p>… which is where <a href="http://labs.episerver.com/en/Blogs/Ted-Nyberg/Dates/112276/8/Run-a-scheduled-job-as-a-specific-EPiServer-user/">Ted Nyberg’s blog post</a> came in. I went ahead and created a process account, checked if this was being run by a scheduled job or if it was running by a person, and then defaulted to a process account in a situation when no CurrentPrincipal.Identity.Name existed… this creates the page programmatically on a schedule and sets the default CreatedBy to whatever account you specify. Back in business, no oracle errors.</p>
<pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">string</span> Execute()
{
<span class="kwrd">if</span> (PrincipalInfo.CurrentPrincipal.Identity.Name == <span class="kwrd">string</span>.Empty)
{
PrincipalInfo.CurrentPrincipal = PrincipalInfo.CreatePrincipal(<span class="str">"test_account"</span>);
}
<span class="kwrd">int</span> Records = GetRecords();
<span class="kwrd">return</span> <span class="str">"Content succesfully imported. "</span> + Records + <span class="str">" blog comments entered into the system."</span>;
}</code></pre>
<p>Incidentally, if you do have this error and need to get rid of it.. you can either delete the pages that were created programmatically, re-save and publish them (which will set the CreatedBy value to your name) or you can edit tblWorkPage where FKPageID=the new page ID and change the ChangedByName to your username.. any of these will fix it.</p>
<p>Special thanks to <a href="http://world.episerver.com/System/Users-and-profiles/Community-Profile-Card/?encryptedcurrentid=ngIucFAWUyS7cfjDlZ5EbYvsdXlLF0gCT034pbAI0o0%3D">Deane Barker</a> for helping to come up with the solution to this problem</p>
<style type="text/css"><![CDATA[
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]></style>Changing The Base Language of an EPiServer CMS 5 Site/blogs/Hans/Dates/2010/6/Changing-The-Base-Language-of-an-EPiServer-CMS-5-Site/2010-06-21T20:51:34.0000000Z<p>The decision was made in 2009 to convert the language branch of all of our web sites from the previously existing /en/ locale to /en-US/. This gives us more flexibility to denote both the language and the region that a particular site is geared towards.</p> <p>Unfortunately, there is no “clean” or “easy” way to do this in EPiServer CMS 5…but this is how I’ve accomplished this conversion on four separate (and very large) web sites:</p> <p>Note: This information was gathered through my trial and error process and a wonderful blog post by Fredrik Haglund posted here: <a title="http://blog.fredrikhaglund.se/blog/2007/12/04/episerver-globalization-and-dynamic-properties/" href="http://blog.fredrikhaglund.se/blog/2007/12/04/episerver-globalization-and-dynamic-properties/">http://blog.fredrikhaglund.se/blog/2007/12/04/episerver-globalization-and-dynamic-properties/</a></p> <p> </p> <p>Here is how I converted my sites from /en/ to /en-US/</p> <ol> <li>We’ll be playing with content at the database level today folks, so first I would advise you to backup your entire database (in my instance it’s Oracle but the table names are the same in SQL Server by the way…). When big changes like these happen, I like to work in a different environment than the one people always work in… so I start by copying my database and VPP folders to a different server, set up the site there, and begin the actual conversion…</li> <li>I like to lock out all my CMS users through web.config while these changes are being made… change the Allow Users option to an Administrative Group in web.config to do this.</li> <li>Enable your new language in EPiServer if it hasn’t been done already. (<strong>Admin –> Config –> Manage Web Site Languages</strong>)</li> <li>Open up your database (newly copied or backed up hopefully) and navigate to the table <strong>tblLanguageBranch</strong>. Note the <strong>PKID</strong> of the value you want to change.</li> <li>Delete the row of <u>the language you want to change to</u>, in my case, this would be the row with a <strong>PKID</strong> of 40 and a <strong>LanguageID</strong> of <em>en-us</em>.</li> <ol> <li>Note: you should have no primary key constraint violations since you just activated the new language, but if you do.. first go to <strong>tblPageLanguageSetting</strong> and delete the offending row there (the offending record will have a FKLangaugeBranchID equal to the one you deleted in step 4, in my case, 40). You might have to go through some of the other content tables and delete records - let the primary key violations be your guide here, my sites had no /en-us/ pages to begin with, so I did not have to perform this step.</li> </ol> <li>Change the <strong>LanguageID</strong> value of the main base language to the new value. In my case, I changed <em>en</em> to <em>en-US</em>. </li> <ol> <li>Note: Fredrik’s blog mentions creating a new row and filling it with the content of the old one (His Step 4)..this is not necessary unless you want to reuse the old language, and you can automatically do this if you want just by turning it back on in the Manage Web Site Languages option through Admin Mode.</li> </ol> <li>Restart EPiServer using iisreset on the machine in question.</li> <ol> <li>Note: We have a script internally that flushes the app pool cache.. this does NOT work, you have to physically start and stop IIS.</li> </ol> <li>At this point, you’re basically done with the conversion..but I always have to perform cleanup, as some things won’t always match up exactly.</li> <ol> <li>Fredrik’s blog mentions checking the LangaugeID value in the <strong>tblKeyword, tblPage, tblPageLanguage, tblWorkPage, </strong>and <strong>tblPageTypeDefault </strong>pages to the right value. I have never had to do this in EPiServer CMS 5.</li> <li>Dynamic Properties hold their values even if you change the /en-US/ – make sure you go through and reset your Dynamic Properties using the correct language/locale combination through Edit Mode.</li> <li>Some page links keep their values – we always go through page by page to make sure all pages link to the correct language and locale.</li> <li>After all is said and done, we run our whole site through Xenu’s Link Sleuth program (<a title="http://home.snafu.de/tilman/xenulink.html" href="http://home.snafu.de/tilman/xenulink.html">http://home.snafu.de/tilman/xenulink.html</a>) to check for any links pointing to /en/. This helps <u>a lot</u> to make sure everything looks right.</li> </ol> <li>Once everything checks out, we move the (now edited) database back into the CMS, allow users to login again through web.config, and away we go…</li> </ol> <p> </p> <p>. </p>Adding an Additional Layer of Logging to EPiServer Actions/blogs/Hans/Dates/2010/5/Adding-an-Additional-Layer-of-Logging-to-EPiServer-Actions/2010-05-11T16:54:00.0000000Z<p>Part of my standard Workflow Implementation is an additional layer of logging that sits in a table outside of the standard EPiServer installation. I’ve found that keeping a record of a few basic EPiServer actions can really help to paint an accurate picture of which editors are doing what.</p> <p>To log the actions as they are executed, I first created a custom property with the [PagePlugIn] Attribute. I then created a method called Initialize, which adds additional processing capabilities to events exposed through DataFactory.Instance. </p> <p>The result is something like the following:</p> <pre class="language-csharp"><code> [PagePlugIn]
<span class="kwrd">public</span> <span class="kwrd">class</span> CustomPageActionControl
{
<span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> Initialize(<span class="kwrd">int</span> bitflags)
{
DataFactory.Instance.SavedPage += LogSaved;
DataFactory.Instance.PublishedPage += LogPublished;
DataFactory.Instance.MovingPage += CheckIfDeleting;
}
<span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">void</span> LogSaved(<span class="kwrd">object</span> sender, PageEventArgs e)
{
LoggingTools.LogVersionStatus(e.Page, <span class="str">"Saved"</span>, <span class="str">"Details: Version saved."</span>, EPiServer.Security.PrincipalInfo.Current);
}
<span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">void</span> LogPublished(<span class="kwrd">object</span> sender, PageEventArgs e)
{
LoggingTools.LogVersionStatus(e.Page, <span class="str">"Published"</span>, <span class="str">"Details: Version published"</span>, EPiServer.Security.PrincipalInfo.Current);
}
<span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">void</span> LogReadyToPublish(<span class="kwrd">object</span> sender, PageEventArgs e)
{
LoggingTools.LogVersionStatus(e.Page, <span class="str">"Publish Requested"</span>, <span class="str">"Details: Version marked as Ready to Publish. "</span>, EPiServer.Security.PrincipalInfo.Current);
}
}</code></pre>
<style type="text/css"><![CDATA[
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]></style>
<p>This could be enhanced to log additional system actions..or be made more flexible so I’m only calling one Log procedure and then have some logic within to determine what the appropriate message should be… but you get the idea.</p>Creating Dynamic Year Values/blogs/Hans/Dates/2010/5/Creating-Dynamic-Year-Values/2010-05-03T18:48:00.0000000Z<p>I absolutely hate when forms and web sites lack common intelligence. Copyright years should always show the current year. Year Dropdown Lists should always have the most recent values. We’re living in a dynamic world where information is always changing.. there’s no excuse for having static content hard coded into page templates!</p> <p>I use this little function to get an ArrayList of the current year and the two years previous to it. There’s really nothing to it…. but by calling this, all the “Select a Year” fields on my forms update every year, without a single line of hardcoded values needing to be changed. Ever again.</p> <pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">static</span> ArrayList GetAcceptableYears()
{
ArrayList AcceptableYears = <span class="kwrd">new</span> ArrayList();
AcceptableYears.Add(DateTime.Now.Year - 2).ToString();
AcceptableYears.Add(DateTime.Now.Year - 1).ToString();
AcceptableYears.Add(DateTime.Now.Year).ToString();
<span class="kwrd">return</span> AcceptableYears;
}</code></pre>
<style type="text/css"><![CDATA[
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]></style>Getting Friendly, International URLs/blogs/Hans/Dates/2010/4/Getting-Friendly-International-URLs/2010-04-30T21:15:00.0000000Z<p>It’s generally best practice to display friendly URL’s to web visitors instead of the standard PageLink attribute that the system uses (A URL like <a href="http://www.somesite.com/en-us/MyProductCategory/MyProduct/">http://www.somesite.com/en-us/MyProductCategory/MyProduct/</a> looks a lot nicer than <a href="http://www.somesite.com/PageTemplates/HomePageTemplate.aspx?id=643&epslanguage=en-US">http://www.somesite.com/PageTemplates/HomePageTemplate.aspx?id=643&epslanguage=en-US</a>)</p> <p>To that end, I often find myself using a procedure called GetFriendlyURL, which takes a PageData object and a Boolean value, and returns a friendly URL based on the UrlRewriteProvider.ConvertToExternal function. It goes something like this:</p> <pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">string</span> GetFriendlyUrl(PageData pd, <span class="kwrd">bool</span> Absolute)
{
UrlBuilder url = <span class="kwrd">new</span> UrlBuilder(pd.LinkURL);
EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, pd.PageLink, UTF8Encoding.UTF8);
<span class="kwrd">if</span> (!Absolute)
{
<span class="kwrd">return</span> url.ToString();
}
<span class="kwrd">else</span>
{
<span class="kwrd">return</span> GetBaseUrl() + url.ToString();
}
}</code></pre>
<style type="text/css"><![CDATA[
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]></style>
<p>Unfortunately, I’ve noticed that this procedure tends to always return the link in the form that the PageData object was originally created in… so if I’m on a different locale, I’ll often get links returned that include the /en-us/ (our base language) even though they should technically be returning links in the locale the user is currently in.</p>
<p>I address this issue with a simple string replacement procedure, as seen below:</p>
<pre class="language-csharp"><code> <span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">string</span> FetchInternationalLink(String InternationalLink, String LanguageVersion)
{
<span class="kwrd">return</span> InternationalLink.Replace(<span class="str">"en-us"</span>, LanguageVersion.ToLower());
}</code></pre>
<style type="text/css"><![CDATA[
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]></style>
<p>Voila – clean, friendly, internationalized URL’s in just a few lines of code.</p>Creating an Oracle view of articles to display EPiServer Content in Lotus Notes/blogs/Hans/Dates/2009/6/Creating-an-Oracle-view-of-articles-to-display-EPiServer-Content-in-Lotus-Notes/2009-02-03T15:33:00.0000000Z<p>We’re currently building out our corporate intranet pages in EPiServer, after many years of being housed in the Lotus Domino arena. While EPiServer does a great job of serving pages, we’ve got lots and lots of custom Domino apps that still need to be accessed that we probably won’t be converting completely to .NET any time soon. For this reason, we’ve got to make EPiServer a bit more flexible to work with the other systems.</p> <p>One of these applications is a mail program that allows our Corporate Communications to select an article posted to our Intranet, pull some content from it, and mail a snapshot of the content to a particular group (managed in our Lotus Notes global directory). So in this instance, what I had to do was create a view that Lotus Notes could access in order to generate these messages.</p> <p>The first step was determining which fields needed to be available to our Lotus Notes program. For this program, Lotus Notes needed to access the Page ID, Name, the SEO-friendly link name, the date the article was published, the type of page being published, and a portion of the body’s content.</p> <p>After some investigation, I found all of these fields in a combination of the TBLTREE, TLPROPERTY and TBLPAGELANGUAGE tables in the EPiServer database. I then isolated the two parent ID’s that I wanted to grab children of. For our instance, we wanted our blog posts (ID = 566), and our news items (ID = 572). I then determined the Page Definition ID’s for the body (content) fields that we needed, decided upon a nesting level (which directly correlated with our URL or page structure syntax) for each of these (2 and 4) and decided to grab only articles posted in English.</p> <p>As an aside, I’m not a big fan of hard coding values, but this needed to be a purely Oracle View, meaning the EPi libraries were not used at all in this determination. Furthermore, our page structure is always going to follow the same nesting syntax and the base parents 566 and 572 will not be going anywhere – so I think we were safe in this instance.</p> <p>Without further ado, the code itself. . </p> <p>CREATE OR REPLACE VIEW MY_VIEW_NAME <br />( PAGEID, NAME, LINKNAME, PUBLISHDATE, PAGELEVEL, CONTENT ) <br />AS <br />SELECT DISTINCT lang.FKPageId <br />    , lang.NAME <br />    , lang.URLSEGMENT <br />    , lang.STARTPUBLISH <br />    , tree.NESTINGLEVEL <br />    , dbms_lob.SUBSTR(LONGSTRING, 4000, 1) <br />FROM <br />    TBLTREE tree <br />    , TBLPROPERTY prop <br />    , TBLPAGELANGUAGE lang  <br />WHERE <br />    (   <br />        ( <br />            tree.FKPARENTID = 572 -- 572 is news parent <br />                AND tree.NESTINGLEVEL = 2 -- level two using the syntax Month-Year/article-name <br />                AND (prop.FKPAGEDEFINITIONID = 244) --244 is main body field for news articles <br />                AND (lang.FKPAGEID = prop.FKPAGEID) <br />        )          <br />        OR <br />        ( <br />            tree.FKPARENTID = 566 -- 566 is blog parent <br />                AND tree.NESTINGLEVEL = 4 -- level four using the syntax Dates/Year/Month/article-name <br />                AND (prop.FKPAGEDEFINITIONID = 289) --289 is main body field for blog articles <br />                AND (lang.FKPAGEID = prop.FKPAGEID) <br />        ) <br />    ) <br />    AND (lang.FKLANGUAGEBRANCHID = 1) -- Only grab English titles <br />    AND (tree.FKCHILDID = lang.FKPAGEID) </p> <p>WITH READ ONLY <br />/    </p> <p>So now we have a nice little view containing the ID, Name, URL Segment, Publish Date, Nesting Level and 4000 characters of the Body’s content. On the Lotus Notes side, a query is made against this view to determine whether or not the nesting level is either a 2 (indicating a news post) or a 4 (indicating a blog post), and then the ID is appended to the proper link format based on the type of post:  <a href="http://mydomain/public/PageTemplates/Blog/Pages/item.aspx?id">http://mydomain/public/PageTemplates/Blog/Pages/item.aspx?id=</a> for blog items or <a href="http://mydomain/public/PageTemplates/News.aspx?id">http://mydomain/public/PageTemplates/News.aspx?id</a>= for news items. That’s all there is to it.</p>