Visual aid for finding page usages directly in EPiServer edit mode page tree extending node tooltips

Recently, I have found myself wanting to know what pages are referencing certain other pages, or sub pages for that matter, while surfing around in the EPiServer edit mode. Having worked with control adapters to extend built-in EPiServer functionality quite a lot lately, I decided to create a simple one that would make my life easier. Code at GitHub as usual.

EPiServer Edit Mode Page Tree Node Tooltips extended with references to pages where it is used, this page is used on other pages.

EPiServer Edit Mode Page Tree Node Tooltips extended with references to pages where it is used, this page is not used on any pages.

There is really nothing complicated involved, all the adapter does is attaching an additional handler to the PageTreeViewItemDataBound event (22) during OnInit.

PageTreeViewTooltipAdapter.cs

public class PageTreeViewTooltipAdapter : ControlAdapter
{
  protected override void OnInit(EventArgs e)
  {
    base.OnInit(e);

    var treeView = Control as PageTreeView;
    if(treeView == null)
    {
      return;
    }
    treeView.PageTreeViewItemDataBound += AddTooltipHandler;
  }

The handler then passes the page’s PageReference to the FindUsagesFor method (32) concatenating the results into a comma separated string; see section below. In order to add our new line of text to the EPiServer page tree tooltip, it is first necessary to create a new Attribute (33) that EPiServer will use assembling data.

private static void AddTooltipHandler(object sender, PageTreeViewEventArgs e)
{
  var page = e.Item.DataItem as PageData;
  if(page == null)
  {
    return;
  }
  var ids = string.Join(", ", FindUsagesFor(page.PageLink));
  e.Item.Attributes.Add("UsedOn", ids);
}

EPiServer uses language files to define the tooltip markup. Hence, it is only a matter of adding the above UsedOn (33) identifier to the proper place (12) in order for it to render.

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<languages>
  <language name="English" id="en">
    <edit>
      <pageexplorer>
        <tooltip>
          Page Type: [PageTypeName]&lt;br/&gt;
          Created: [Created]&lt;br/&gt;
          Changed: [Changed]&lt;br/&gt;
          Page ID: [PageLink]&lt;br /&gt;
          Sort index: [PagePeerOrder][AdditionalInformation]&lt;br /&gt;
          Used on: [UsedOn]</tooltip>

Finding pages linking to the current page or one of its decending pages

Retriving the ids of all the pages that are referencing the current page or one of its decending pages is the easiest thing in this solution; the good people over at EPiServer has already done it for us. Feeding the method GetLinksToPages (39) with a PageReference will return a PageReferenceCollection containing what we want. If we do a bit of reflectoring we see that it also takes other page providers into account, not just the standard EPiServer.LocalPageProvider.

private static IEnumerable<string> FindUsagesFor(PageReference pageReference)
{
  var references = DataFactory.Instance.GetLinksToPages(pageReference);
  if(references == null || !references.Any())
  {
    return new[] {"-"};
  }
  return references.Select(reference => reference.ID.ToString(CultureInfo.InvariantCulture));
}

I am aware that the Used on label does not accurately describe what it is that I am showing, but I blame a need to keep the text short together with a temporary lack of imagination. This was good enough to fulfill my requirements, but if you would like to single out the dependencies of a single page (not looking at decendants), I would suggest taking a peek at how EPiServer does things. Interesting methods may include EPiServer.DataFactory.GetLinksToPages(..), EPiServer.LocalPageProvider.GetReferencesToPages(..) as well as EPiServer.LocalPageProvider.GetReferencesToPageAndDescendents(..).

Posted in EPiServer | Tagged C#, Control Adapter, EPiServer, Event Handlers, ItemDataBound event, language files, PageTree | Leave a comment

Think things through, or: Simple way to 404 Not Found instead of EPiServer login screen for unpublished pages

I spent some time today attempting to make visitors land on a custom 404 Not Found error page when surfing to EPiServer pages that are no longer published; i.e. pages where the StopPublish date has passed (click here to skip my rant and go directly to the solution). The behaviour that we were experiencing on our site was an automatic redirect to EPiServer’s /Util/login.aspx with a ReturnUrl parameter, which was not desirable. Having recently written a redirect module for a custom login functionality, I first failed trying a similar approach to this problem.

Failing 404 module

public class MyModule : IHttpModule
{
  public void Init(HttpApplication context)
  {
    context.BeginRequest += My404Handler;
  }

The brilliant idea to attach a new event handler to the BeginRequest event, catching things early on, turned out to work a little bit too well. At this point, EPiServer’s authentication code has not been run yet, resulting in no way of knowing whether the user is logged in or not. Hence, an editor surfing to the page in EPiServer’s Edit Mode ended up getting the 404 Not Found page as well.

Failing 404 module, revisited

public class MyModule : IHttpModule
{
  public void Init(HttpApplication context)
  {
    context.PostAuthenticateRequest += My404Handler;
  }

  private void My404Handler(object sender, EventArgs e)
  {
    var application = sender as HttpApplication;
    var context = new HttpContextWrapper(application.Context);
..
    var url = new UrlBuilder(context.Request.Url);

So, after reattaching the handler to the PostAuthenticationRequest event, the context.User.Identity.IsAuthenticated gave a much better result. The only problem now was that during the PostAuthenticationRequest event, unauthorized visitors have already had their context.Request.Url:s redirected to EPiServer’s /Util/login.aspx. So. Fail. Again.

Eleven lines of code to properly 404 Not Found on unpublished EPiServer pages

Doing what I should have done from the start, I had a look in the EPiServer documentation. It seems like there is already a brilliant way of handling this problem in just a few lines of code. By overriding the AccessDenied method in our page base (inheriting from the EPiServer.TemplatePage; the virtual AccessDenied method is located on the EPiServer.PageBase class), I was able to do my date check rather easily.

TemplatePageBase.cs

public abstract class TemplatePageBase<TPage> : EPiServer.TemplatePage, IStandardPage
    where TPage : PageData
{
 public override void AccessDenied()
 {
  if (CurrentPage.StopPublish <= DateTime.Now)
  {
   Context.Response.Status = "404 Not Found";
   Context.Response.StatusCode = (int)HttpStatusCode.NotFound;
   Context.Response.End();
   return;
  }
  base.AccessDenied();
 }
}

AccessDenied is run whenever EPiServer decides that a visitor is not authenticated, giving you all the opportunities in the world to do whatever you want about it.

Note: Perhaps a 404 Not Found page is not what you should be showing at this point; perhaps what you really are looking for is a 410 Gone page. What do unpublished pages mean on your site?

Posted in EPiServer | Tagged 404, 410, C#, EPiServer, Error pages | 6 Comments

Revisited and improved: PageType based filtering of EPiServer’s EditMode PageTree

Some time ago I developed a small plug-in to EPiServer‘s edit mode page tree, allowing web editors to filter its pages based on all available page types. I decided to spend this weekend in the sun on my balcony with my laptop extending the functionality a great deal. The code is as usual available over at GitHub, and also as a NuGet package in the EPiServer NuGet feed.

Apart from employing the Model-View-Presenter paradigm with unit testing, the largest improvement is the possibility to select which page types that should be available in the filter DropDownList; both on a global as well as on a user specific level. Logging and translation has also been added using log4net and the build-in EPiServer translation language file functionality.

Global PageTypeTreeFilter settings using EPiServer Dynamic Data Store

The global settings for the PageTypeTreeFilter are persisted using EPiServer’s Dynamic Data Store; so it will not work for earlier versions of the EPiServer CMS. Should you still want to use it, you will have to create your own implementation of the IGlobalSettingsRepository interface.

Global settings interface link for the PageTypeTreeFilter functionality

The global settings are available through a Settings link right next to the PageType DropDownList in EPiServer’s Edit Mode. This link will only be shown to users that are members of specific EPiServer roles; defined in the web.config file as seen below.

web.config

<applicationSettings>
 <PageTypeTreeFilter.Properties.Settings>
  <setting name="GlobalSettingsRoles" serializeAs="String">
   <value>WebAdmins, Administrators</value>
  </setting>
 </PageTypeTreeFilter.Properties.Settings>
</applicationSettings>

When installing the NuGet package or looking at the code on GitHub, the default roles for this are the administrator roles (87), but may of course be changed at your discretion.

PageTypeTreeFilter GlobalSettings  Revisited and improved: PageType based filtering of EPiServer’s EditMode PageTree

In the global settings interface you may specify which page types that should be included in the DropDownList by moving them to the Selected page types box; either by using the arrows, or by double clicking on them. This works just like the EPiServer Role boxes over in the Admin Mode. At the top there is a checkbox allowing you to let users override your global settings, should you like to grant your web editors that freedom. The Reset to default button will do just this by removing everything from the Dynamic Data Store.

User specific PageTypeTreeFilter settings with the built-in EPiServer User Profile functionality

The user specific settings employs EPiServer’s user profile functionality to store its values. An extra tab has been added when clicking on the My Settings menu alternative as seen below.

PageTypeTreeFilter UserSettings  Revisited and improved: PageType based filtering of EPiServer’s EditMode PageTree

Here each user may define their own available page types if their web administrator allows them to. It works the same way as the global settings, with the difference that there is no reset button. Should the user want to, they can choose not to use their own settings, but rather go with the global ones. If the web administrator did not check the Allow users to override..-checkbox, the whole interface will be disabled and showing an explanation blaming the web admin.

PageTypeTreeFilter UserSettings Deactivated  Revisited and improved: PageType based filtering of EPiServer’s EditMode PageTree

Posted in ASP.Net, EPiServer, EPiServer CMS 6 R2, Plugins | Tagged C#, Dynamic Data Store, EPiServer, EPiServer user profiles, PageTypeTreeFilter | Leave a comment

Being friends with the PropertyControlClassFactory, or: 101 ways to change EPiServer built-in property appearances

As I have been playing around with using ControlAdapters to change the rendering of built-in EPiServer properties lately, I got curious on what else I could possible do to modify them. As it turns out, the EPiServer PropertyControlClassFactory is a great ally in doing this. Hence, this post mostly revolves around different ways of getting it to do what you want. The example source code is available at GitHub through this link.

To show the result of all the examples in this post, I use a line of code that I just threw into the EPiServer AlloyTech sample project footer; hence the pretty blue background in all the images. The default behaviour of this code would be the rendering of a text containing the current page’s name.

<EPiServer:Property runat="server" PropertyName="PageName" />

The RenderingChangedAppearance class of each example looks very much all the same, except that they render a slightly different text depending on which method was used; we concatenate the original output which we get from the ToWebString method (12) with a string of our own (13).

RenderingChangedAppearance.cs

namespace EPiServerBuiltInProperties.CustomizeAppearances .UsingPlugInAttribute
{
  public class RenderingChangedAppearance : PropertyStringControl
  {
    public override void CreateDefaultControls()
    {
      this.Controls.Add(new Literal
        {
          Text = string.Concat(this.ToWebString(),
              " appearance changed using PlugInAttribute.")
        });
    }
  }
}

Change the rendering of a built-in EPiServer property using a plugin attribute

Our first example on how to use the PropertyControlClassFactory to register a new class mapping, making EPiServer use it rather than the default one, is by extending the PlugInAttribute class. When we add a static Start method, it will be called by the EPiServer plugin system during startup; as described by Magnus Stråle in this post on initialization in EPiServer

RegisterAppearance.cs

public class RegisterAppearance : PlugInAttribute
{
  public static void Start()
  {
    PropertyControlClassFactory
       .Instance
       .RegisterClass(typeof(PropertyString),
                      typeof(RenderingChangedAppearance));
  }
}

The result of this is that when we surf to for instance the page named News and Events in the EPiServer AlloyTech sample project we can see that the default rendering does no longer apply; instead we get our own slightly enhanced one.

EPiServer Build-In Property Appearance changed using a PlugInAttribute and PropertyControlClassFactory.

Change the rendering of a built-in EPiServer property using an initialization module

The second example involves creating your own initialization module and having the PropertyControlClassFactory register the mappings there. In order for any initialization module to be run at the start of the application it must at least implement the IInitializableModule interface (or one of its sub interfaces, like IInitializableHttpModule as seen below) and be marked with the [InitializableModule] attribute. Should your initialization module fail and throw an exception during startup, it will re-execute every time a request is made to your application until the error disappears. If you are interested there is plenty of information on this over at EPiServer World.

AppearanceInitializationModule.cs

[InitializableModule]
public class AppearanceInitializationModule : IInitializableHttpModule
{
  public void Initialize(InitializationEngine context) {}
  public void Uninitialize(InitializationEngine context) {}
  public void Preload(string[] parameters) {}

  public void InitializeHttpEvents(HttpApplication application)
  {
    PropertyControlClassFactory
       .Instance
       .RegisterClass(typeof(PropertyString),
                      typeof(RenderingChangedAppearance));
  }
}

Set up your class mappings in the InitializeHttpEvents method and let EPiServer do the rest. Surfing to the News and Events page will give us the following output in the page footer.

EPiServer Build-In Property Appearance changed using an initialization module and PropertyControlClassFactory.

Change the rendering of a built-in EPiServer property using Global.asax

A third option for changing the default appearance of built-in EPiServer properties is through the Global.asax.cs file. In my own humble opinion, this may not be the prettiest way of doing it, but it can be done nonetheless. The key is to have the PropertyControlClassFactory register your mappings in an override of the Init method (8).

Global.asax.cs

public class Global : EPiServer.Global
{
  public override void Init()
  {
    PropertyControlClassFactory
       .Instance
       .RegisterClass(typeof(PropertyString),
                      typeof(RenderingChangedAppearance));
    base.Init();
  }

Once built, the code will output the text as expected when surfing to the example page.

EPiServer Build-In Property Appearance changed using Global.asax and PropertyControlClassFactory.

Change the rendering of a built-in EPiServer property using web.config

The fourth way of having the PropertyControlClassFactory register mappings for our classes brings us into the web.config file; or more specifically, into the classFactories section of the web.config file. I have removed loads of configuration in the snippet below as well as from the example code at GitHub, but the hierarchy of the elements is correct.

web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <episerver.baseLibrary>
    <classFactories>
      <add id="PropertyControlFactory"
           type="EPiServer.Core.PropertyControlClassFactory, EPiServer">
        <register
            type="EPiServer.Core.PropertyString, EPiServer"
            mappedType="EPiServerBuiltInProperties .CustomizeAppearances.UsingWebConfig.RenderingChangedAppearance, EPiServerBuiltInProperties" />

By registering a mapping (7-9) in the classFactories section of the episerver.baseLibrary it is possible to have EPiServer use our customized rendering rather than the default one; just tell the configuration which control it should be swapping out and which to use instead. This time our sample AlloyTech page yields the following.

EPiServer Build-In Property Appearance changed using web.config and PropertyControlClassFactory.

Change the rendering of a built-in EPiServer property using a control adapter

In the final example of this post we will use one of my favourite hammers to solve this problem; a golden control adapter. All jokes aside, changing the appearance may also be done in this way. If you would like to read more on adapters I have some previous posts on the subject.

The way we render the changes is different from when we did an extension of the PropertyStringControl class. Now we extend ControlAdapter (7) and override the Render method (9) instead. The rendering may still be changed, however, by adding controls to the control collection as before.

RenderingChangedAppearance.cs

public class RenderingChangedAppearance : ControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Control.Controls
      .Add(new Literal
           {
             Text = " appearance changed using a ControlAdapter."
           });
    base.Render(writer);
  }
}

The mappings are in this case done through an AdapterMappings.browser file in the App_Browsers folder of your web project. Here is what it would look like for our example control adapter in the code snippet above.

AdapterMappings.browser

<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter controlType= "EPiServer.Web.PropertyControls.PropertyStringControl"
               adapterType="EPiServerBuiltInProperties .CustomizeAppearances.UsingControlAdapter .RenderingChangedAppearance" />
    </controlAdapters>
  </browser>
</browsers>

As expected, the rendering of our example text on the News and Events page has changed once more.

EPiServer Build-In Property Appearance changed using a control adapter and PropertyControlClassFactory.

Order of business, or: In what order will these mappings get applied?

On a whim I decided to implement all of the examples at the same time, then removing them one by one to see if I could get an order in which they were applied. What I ended up with was the following.

  1. ControlAdapter
  2. Web.config
  3. PlugInAttribute
  4. Global.asax
  5. InitializableModule

As I know far too little about the inner workings of ASP.NET and EPiServer, I would not bet any vital parts of my system on this order always being the same. However, I found it rather interesting to test though.

Presentation control for properties in Admin mode

It is possible to select different presentation controls for rendering EPiServer properties through Admin Mode. Linus Ekström has written an interesting post about it over at EPiServer labs.

EPiServer Build-In Property Appearance changed, Multiple ways of rendering.

Posted in Control adapters, EPiServer | Tagged C#, Control Adapter, Customized appearance, EPiServer, Initialization module, PlugInAttribute, PropertyControlClassFactory | Leave a comment

The <EPiServer:Property .. /> just for fun, what output really gets generated for those PageData Property Members?

I created a few test page types along with some templates and EPiServer pages in order to test outputs from <EPiServer:Property .. />. The HTML comments are not part of the output, obviously, and I cleaned away attributes such as ids, inline styles and CSS classes. Furthermore, I chose to be blind to how useful outputting some of these properties like this really is; you probably wouldn’t, and probably shouldn’t. Where it is applicable, there is also a note on what is rendered in Edit mode for the input controls setting these properties. The order is the one from the EPiServer SDK on PageData Members.

PropertyName=”PageArchiveLink”

The name of the EPiServer archive page My Page Archive making the page name in web address My-Page-Archive. Page ID is 72 and the template file is named ArchivePage.aspx.

<EPiServer:Property PropertyName="PageArchiveLink" runat="server" />
<!-- View mode, Friendly URLs on -->
<a href="/en/My-Page-Archive/">My Page Archive</a>

<!-- View mode, Friendly URLs off -->
<a href="/Templates/ArchivePage.aspx?id=72&epslanguage=en">My Page Archive</a>

<!-- Edit mode -->
<input type="hidden" value="72">
<input type="text" disabled="disabled" size="30" value="My Page Archive [72]">
<input type="button" onclick="EPi.CreatePageBrowserDialog(..);" value="...">

<!-- Edit mode - Preview tab, Friendly URLs on -->
<a href="/en/My-Page-Archive/?id=72">My Page Archive</a>

<!-- Edit mode - Preview tab, Friendly URLs off -->
<a href="/Templates/ArchivePage.aspx?id=72&epslanguage=en">My Page Archive</a>

PropertyName=”PageCategory”

The page has the EPiServer AlloyTech sample Categories Event, Example and Product selcted on the Categories tab.

<EPiServer:Property PropertyName="PageCategory" runat="server" />
<!-- View mode -->
<span>Event, Example, Product</span>

<!-- Edit mode (one of them) -->
<input type="checkbox" checked="checked">
<label>Event</label>

<!-- Edit mode - Preview tab -->
<span>Event, Example, Product</span>

PropertyName=”PageChanged”

<EPiServer:Property PropertyName="PageChanged" runat="server" />
<!-- View mode -->
<span>2/29/2012 2:11:44 PM</span>

<!-- Edit mode - Preview tab -->
<span>2/29/2012 2:11:44 PM</span>

PropertyName=”PageChangedBy”

The page was changed using my test user mathiaskunto. On-Page Edit gives you an input textbox, but you may find it somewhat difficult updating the value like this.

<EPiServer:Property PropertyName="PageChangedBy" runat="server" />
<!-- View mode -->
<span>mathiaskunto</span>

<!-- On-Page Edit -->
<input type="text" value="mathiaskunto" autocomplete="off">

<!-- Edit mode - Preview tab -->
<span>mathiaskunto</span>

PropertyName=”PageCreated”

<EPiServer:Property PropertyName="PageCreated" runat="server" />
<!-- View mode -->
<span>2/28/2012 4:15:34 PM</span>

<!-- Edit mode -->
<input type="text" size="20" maxlength="20" value="2012-02-28 16:15">
<input type="button" onclick="EPi.CreateDateBrowserDialog(..);" value="...">

<!-- Edit mode - Preview tab -->
<span>2/28/2012 4:15:34 PM</span>

PropertyName=”PageCreatedBy”

The page was also created using my test user mathiaskunto.

<EPiServer:Property PropertyName="PageCreatedBy" runat="server" />
<!-- View mode -->
<span>testuser</span>

<!-- On-Page Edit -->
<input type="text" value="testuser" autocomplete="off">

<!-- Edit mode - Preview tab -->
<span>testuser</span>

PropertyName=”PageLanguageBranch”

The language branch of the EPiServer test page is English.

<EPiServer:Property PropertyName="PageLanguageBranch" runat="server" />
<!-- View mode -->
<span>en</span>

<!-- On-Page Edit -->
<input type="text" value="en" autocomplete="off">

<!-- Edit mode - Preview tab -->
<span>en</span>

PropertyName=”PageLinkURL”

The name of the EPiServer page is News and Events making the page name in web address News-and-Events. Page ID is 178 and the template file is named TestPage.aspx.

<EPiServer:Property PropertyName="PageLinkURL" runat="server" />
<!-- View mode, Friendly URLs on -->
<a href="/News-and-Events/">/Templates/TestPage.aspx?id=178</a>

<!-- View mode, Friendly URLs off -->
<a href="/Templates/TestPage.aspx?id=178">/Templates/TestPage.aspx?id=178</a>

<!-- Edit mode - Preview tab, Friendly URLs on -->
<a href="/News-and-Events/?id=178">/Templates/TestPage.aspx?id=178</a>

<!-- Edit mode - Preview tab, Friendly URLs off -->
<a href="/Templates/TestPage.aspx?id=178">/Templates/TestPage.aspx?id=178</a>

PropertyName=”PageMasterLanguageBranch”

The master language branch is English.

<EPiServer:Property PropertyName="PageMasterLanguageBranch" runat="server" />
<!-- View mode -->
<span>en</span>

<!-- On-Page Edit -->
<input type="text" value="en" autocomplete="off">

<!-- Edit mode - Preview tab -->
<span>en</span>

PropertyName=”PageGuid”

<EPiServer:Property PropertyName="PageGuid" runat="server" />
<!-- View mode -->
<span>038FFF7A-CE04-4590-A7CB-B1F3ECF0718D</span>

<!-- On-Page Edit -->
<input type="text" value="038FFF7A-CE04-4590-A7CB-B1F3ECF0718D" autocomplete="off">

<!-- Edit mode - Preview tab -->
<span>038FFF7A-CE04-4590-A7CB-B1F3ECF0718D</span>

PropertyName=”PageLink”

The name of the EPiServer page is News and Events making the page name in web address News-and-Events. Page ID is 178 and the template file is named TestPage.aspx.

<EPiServer:Property PropertyName="PageLink" runat="server" />
<!-- View mode, Friendly URLs on -->
<a href="/en/News-and-Events/">News and Events</a>

<!-- View mode, Friendly URLs off -->
<a href="/Templates/TestPage.aspx?id=178&epslanguage=en">News and Events</a>

<!-- Edit mode - Preview tab, Friendly URLs on -->
<a href="/en/News-and-Events/?id=178">News and Events</a>

<!-- Edit mode - Preview tab, Friendly URLs off -->
<a href="/Templates/TestPage.aspx?id=178&epslanguage=en">News and Events</a>

PropertyName=”PageName”

The name of the EPiServer test page is News and Events.

<EPiServer:Property PropertyName="PageName" runat="server" />
<!-- View mode -->
<span>News and Events</span>

<!-- On-Page Edit -->
<input type="text"value="News and Events" autocomplete="off">

<!-- Edit mode -->
<input type="text" maxlength="255" value="News and Events">

PropertyName=”PageTypeID”

The ID of the the EPiServer test page is 34.

<EPiServer:Property PropertyName="PageTypeID" runat="server" />
<!-- View mode -->
<span>34</span>

<!-- Edit mode - Preview tab -->
<span>34</span>

PropertyName=”PageTypeName”

The name of the EPiServer test page type is TestPageType.

<EPiServer:Property PropertyName="PageTypeName" runat="server" />
<!-- View mode -->
<span>TestPageType</span>

<!-- On-Page Edit -->
<input type="text" value="TestPageType" autocomplete="off">

<!-- Edit mode - Preview tab -->
<span>TestPageType</span>

PropertyName=”PageParentLink”

The EPiServer test page is placed directly under the Root folder making Root its parent page. Page ID of Root is 1. If it was placed in another place, say under Root -> Start -> Company, the PageParentLink would give the same type of output as for PageLink.

<EPiServer:Property PropertyName="PageParentLink" runat="server" />
<!-- View mode, Friendly URLs on -->
<a href="/secure/ui/CMS/edit/workspace.aspx?id=1">Root</a>

<!-- View mode, Friendly URLs off -->
<a href="/secure/ui/CMS/edit/workspace.aspx?id=1">Root</a>

<!-- Edit mode - Preview tab, Friendly URLs on -->
<a href="/secure/ui/CMS/edit/workspace.aspx?id=1">Root</a>

<!-- Edit mode - Preview tab, Friendly URLs off -->
<a href="/secure/ui/CMS/edit/workspace.aspx?id=1">Root</a>

PropertyName=”PageSaved”

<EPiServer:Property PropertyName="PageSaved" runat="server" />
<!-- View mode -->
<span>3/3/2012 3:13:37 PM</span>

<!-- Edit mode - Preview tab -->
<span>3/3/2012 3:13:37 PM</span>

PropertyName=”PageStartPublish”

<EPiServer:Property PropertyName="PageStartPublish" runat="server" />
<!-- View mode -->
<span>3/2/2012 1:22:33 PM</span>

<!-- Edit mode -->
<input type="text" size="20" maxlength="20" value="2012-03-02 13:22">
<input type="button" onclick="EPi.CreateDateBrowserDialog(..);" value="...">

<!-- Edit mode - Preview tab -->
<span>3/2/2012 1:22:33 PM</span>

PropertyName=”PageStopPublish”

<EPiServer:Property PropertyName="PageStopPublish" runat="server" />
<!-- View mode -->
<span>12/24/2012 3:00:00 PM</span>

<!-- Edit mode -->
<input type="text" size="20" maxlength="20" value="2012-12-24 15:00">
<input type="button" onclick="EPi.CreateDateBrowserDialog(..);" value="...">

<!-- Edit mode - Preview tab -->
<span>12/24/2012 3:00:00 PM</span>

PropertyName=”PageURLSegment”

The name of the EPiServer test page is News and Events making the Page name in web address (i.e. URL segment) News-and-Events.

<EPiServer:Property PropertyName="PageURLSegment" runat="server" />
<!-- View mode -->
<span>News-and-Events</span>

<!-- On-Page Edit -->
<input type="text" value="News-and-Events" autocomplete="off">

<!-- Edit mode -->
<input type="text" maxlength="255" value="News-and-Events">

<!-- Edit mode - Preview tab -->
<span>News-and-Events</span>

PropertyName=”PageVisibleInMenu”

The EPiServer test page is marked to be visible in menues on the Settings tab.

<EPiServer:Property PropertyName="PageVisibleInMenu" runat="server" />
<!-- View mode -->
<span>True</span>

<!-- Edit mode -->
<input type="checkbox" checked="checked">

<!-- Edit mode - Preview tab -->
<span>True</span>
Posted in EPiServer | Tagged EPiServer, PageData | Leave a comment

How to embed EPiServer resources such as GuiPlugIns and Pages together with Images, JavaScripts and Stylesheets in droppable assembly binaries

Recently I got slapped back into reality receiving a Twitter message from my collegue Patrik Akselsson in which he kindly asked me to hand him a GitHub link to the code from one of my blog posts. Being long overdue uploading my code into a public repository and constructing NuGet packages for my EPiServer features, I decided to do so for all my relevant posts. While at it, I have also been moving my code away from the EPiServer AlloyTech sample site that I use into separate Visual Studio 2010 projects resulting in the need to embed various resources in assemblies; such as for instance JavaScripts and Stylesheets, as well as pages and user controls. Links to all the sample code in this post may be found at the bottom.

Embedding an EPiServer GuiPlugIn in a binary assembly

The problem with doing this is that the EmbeddedPlugin.aspx file holding the custom EPiServer tool will not like being included in a library class project. But why would it? It probably knows that it will be hard for EPiServer to find it if it is stashed away somewhere without a proper URL. In order to use your aspx and ascx files as embedded resources in a binary assembly, you must first tell Visual Studio that it should treat them as such. This is done by bringing up the Properties pane (the bottom option on the right mouse click context menu) for the file in question, setting the Build Action option to Embedded Resource. The images below show where this is done for the EmbeddedControl.ascx file.

Embedded EPiServer resources, The Visual Studio 2010 solution explorer.Embedded EPiServer resources, The Visual Studio 2010 properties of an embedded resource.

Obviously it is important that you do not touch this setting for the code-behind EmbeddedControl.ascx.cs and its designer file as they need to be compiled into the assembly rather than being used as resources.

The approach that we will take embedding these files is based on using a custom VirtualPathProvider to dynamically load the pages via a resource stream as they are requested. Our EmbeddedResourceProvider class extends VirtualPathProvider and will help us doing so.

EmbeddedResourceProvider.cs

public class EmbeddedResourceProvider : VirtualPathProvider
{
  private static bool IsEmbeddedResourcePath(string virtualPath)
  {
    var path = VirtualPathUtility.ToAppRelative(virtualPath);
    return path.StartsWith("~/EmbeddedResource/", StringComparison.InvariantCultureIgnoreCase);
  }

The first thing that we need is a way to determine whether or not a request was made trying to retrieve one of our embedded resources. I decided to construct these imaginary URLs always starting with ~/EmbeddedResource/ (33). This will allow me to only handle the relevant requests, and pass the other ones up the ladder to the base methods. It may be a good idea to pick a name which you know will not be used as the name of an EPiServer page directly under the Start node, or as a directory with files in the web root; you may regret it if you do.

  public override VirtualFile GetFile(string virtualPath)
  {
    return IsEmbeddedResourcePath(virtualPath) ?
        new VirtualResourceFile(virtualPath) :
        base.GetFile(virtualPath);
  }

As ASP.NET wants to resolve a virtual path, it makes a request to the VirtualPathProvider that was registered most recently and asks if the resource exists. If it does, it makes a call the the GetFile method above to retrieve it. As you can see, we first check if this is an attempt to get our EPiServer GuiPlugIn, or just another visitor. If it indeed is someone trying to access our custom tool, we will need to construct the virtual file ourselves before we can return it; we will get back to how this is done with the VirtualResourceFile class in just a bit. If the request was not made with embedded resources in mind, we simply pass it along to the default GetFile method upstairs. Before we take a look at how to conjure up an imaginary file, there is one last important thing worth mentioning.

  public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
  {
    return IsEmbeddedResourcePath(virtualPath) ?
        null :
        base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
  }

To prevent ASP.NET from meddling in attempting to monitor changes in our non-exisiting file (thus throwing FileNotFound exceptions all over the place), the GetCacheDependency must return a null value; see the examples over at MSDN if you would like to know more.

VirtualResourceFile.cs

public override Stream Open()
{
  var pathArray = _path.Split('/');
  var resource = pathArray[3];
  var assemblyFile = Path.Combine(HttpRuntime.BinDirectory, pathArray[2]);

  var assembly = System.Reflection.Assembly.LoadFile(assemblyFile);
  var stream = assembly.GetManifestResourceStream(resource);
  if(stream == null)
  {
    throw new Exception(string.Format("Unable to get resource '{0}' manifest resource stream from assembly '{1}'.", resource, assemblyFile));
  }
  return stream;
}

The interesting part of the VirtualResourceFile is the Open method overridden from the extended VirtualFile class. This is where we need to parse our fake resource path and load our EPiServer GuiPlugIn. You can probably do this in a more elegant way, but since we know what an embedded resource URL will look like and since we are in control of all the requests being made, it is easier just to do a string split (19). Apart from the ~/EmbeddedResource/ flag, there are two other components that are needed in order to load our resource; an assembly to load from, and the requested resource. The resource URLs will look as follows:

~/EmbeddedResource/{assembly}.dll/{namespace}.{resource filename}

You are free to add your own query parameters at your discresion; we will have a look at doing this in a moment. So, the path is split up into different sections, and knowing which one contains our assembly filename, we can use it to bring up the proper binary (23). Then it is just a matter of calling the GetManifestResourceStream method with the namespace together with the name of our EPiServer GuiPlugIn page (24), and we will be ready to go.

EmbeddedPlugin.aspx.cs

[GuiPlugIn(
  DisplayName = "Embedded Plugin",
  Description = "Plugin that was embedded and distributed as a binary assembly file.",
  Area = PlugInArea.AdminMenu,
  RequiredAccess = AccessLevel.Administer,
  Url = "~/EmbeddedResource/EmbeddedEPiServerResources.dll/EmbeddedEPiServerResources.EmbeddedPlugin.aspx"
)]
public partial class EmbeddedPlugin : SystemPageBase
{

The only thing that is different in the embedded EPiServer GuiPlugIn page code-behind file is the Url attribute parameter (15). In order to have EPiServer go look for the custom tool in the correct place, we need to point it to an address on the form that we previously defined. That is really almost all there is to it; if you would like to pass along query parameters in say for instance an HTML link, just concatenate them at the end like you normally would:

href=”/EmbeddedResource/EmbeddedEPiServerResources.dll/EmbeddedEPiServerResources.EmbeddedPlugin.aspx?id=19&color=red

The request object’s query parameters are not touched by the VirtualPathProvider, so you can handle them as you wish at this end as well.

var container = new HtmlGenericControl("div");
foreach (var key in Request.QueryString.AllKeys)
{
  container
    .Controls
    .Add(new Literal
      {
        Text = string.Format("<p>Query parameter named <strong>{0}</strong> has value <strong>{1}</strong>.</p>", key, Request.QueryString[key])
      });
}

Acquiring embedded resources is no different whether you request them from code-behind, or from code-infront via links or Register directives. It is just the URL that has changed.

<%@ Register TagPrefix="uc" TagName="EmbeddedControl" Src="~/EmbeddedResource/EmbeddedEPiServerResources.dll/EmbeddedEPiServerResources.EmbeddedControl.ascx" %>

<uc:EmbeddedControl runat="server" />

<a href="/EmbeddedResource/EmbeddedEPiServerResources.dll/EmbeddedEPiServerResources.EmbeddedControl.ascx" title="..">Embedded control</a>
var embeddedControl = LoadControl("/EmbeddedResource/EmbeddedEPiServerResources.dll/EmbeddedEPiServerResources.EmbeddedControl.ascx") as EmbeddedControl;

The last thing needed to be done is making ASP.NET use our EmbeddedResourceProvider trying to access virtual paths. This is normally done in one of two different ways (and the web.config file is not one of them) before any page parsing or compilation is performed by the Web application; either by using the RegisterVirtualPathProvider method in the Application_Start event of Global.asax, or through an AppInitialize method defined in the App_Code directory.

Global.asax.cs

public class Global : System.Web.HttpApplication
{
  void Application_Start(object sender, EventArgs e)
  {
    HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourceProvider());
  }

App_Code\AppStart.cs

public static class AppStart
{
  public static void AppInitialize()
  {
    HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourceProvider());
  }
}

The two examples above are modified versions of the ones from the examples section at MSDN. The files are not included in the Visual Studio 2010 sample project, but are located in the ExampleInitialization directory.

Embedding JavaScript, Stylesheet and image resources in a binary assembly

Embedding and using other resources is a whole lot easier than retrieving embedded pages and user controls. There are only a few steps needed to be taken, first of which is setting the BuildAction to Embedded resource for all of the files that you wish to embed; as shown in the images at the top of this post. There is no need for a VirtualPathProvider, and all example code for this section may be found in the EmbeddedControl.ascx.cs file.

EmbeddedControl.ascx.cs

[assembly: System.Web.UI.WebResource( "EmbeddedEPiServerResources.Styles.EmbeddedStylesheet.css", "text/css")]
[assembly: System.Web.UI.WebResource( "EmbeddedEPiServerResources.Styles.Images .Embedded_EPiServer_logo.gif", "image/gif")]
[assembly: System.Web.UI.WebResource( "EmbeddedEPiServerResources.Scripts.EmbeddedJavaScript.js", "application/javascript")]
namespace EmbeddedEPiServerResources
{
  public partial class EmbeddedControl : UserControlBase
  {

After you have fiddled with the BuildAction of your files, it is time to expose them as embedded resources by adding a few lines of code above your namespace (4-6). You may also do this in the AssemblyInfo.cs file in the Properties directory. When this is done, your resources are accessible by calling the ClientScriptManager‘s GetWebResourceUrl method with the proper type and resource, and it is up to you how you would like to use them on your site. If they do not show up, chances are that you made a typo in the {namespace}.{resource name} string.

private void IncludeJavaScript()
{
  var jsPath = Page
      .ClientScript
      .GetWebResourceUrl(typeof(EmbeddedControl), "EmbeddedEPiServerResources.Scripts.EmbeddedJavaScript.js");
  Page.ClientScript.RegisterClientScriptInclude( "EmbeddedEPiServerResources.Scripts.EmbeddedJavaScript.js", jsPath);
}

private void IncludeStylesheet()
{
  var cssPath = Page
      .ClientScript
      .GetWebResourceUrl(typeof(EmbeddedControl), "EmbeddedEPiServerResources.Styles.EmbeddedStylesheet.css");

  var cssLink = new HtmlLink { Href = cssPath };
  cssLink.Attributes.Add("rel", "stylesheet");
  cssLink.Attributes.Add("type", "text/css");
  cssLink.Attributes.Add("media", "screen");
  Page.Header.Controls.Add(cssLink);
}

private void IncludeImage()
{
  var imagePath = Page
      .ClientScript
      .GetWebResourceUrl(typeof(EmbeddedControl), "EmbeddedEPiServerResources.Styles.Images .Embedded_EPiServer_logo.gif");
  EmbeddedImage.ImageUrl = imagePath;
}

The EmbeddedImage (46) above is a normal ASP.NET Image control. After building and surfing to the GuiPlugIn interface in EPiServer’s Admin Mode, all of the embedded resources will be used as expected.

EmbeddedEPiServerResources Plugin Interface in Admin Mode  How to embed EPiServer resources such as GuiPlugIns and Pages together with Images, JavaScripts and Stylesheets in droppable assembly binaries

Source code

Source code: EmbeddedEPiServerResources.zip
GitHub link: https://github.com/matkun/Blog/tree/master/SamplesAndExamples/EmbeddedEPiServerResources

Posted in ASP.Net, EPiServer, Plugins | Tagged C#, Embedded resources, EPiServer, VirtualPathProvider | Leave a comment

Complete and concrete example of what an ASP.NET WebForms Model-View-Presenter project may look like using StructureMap, NUnit and AutoMoq

For quite some time now I have been thinking of creating some sort of sample project to show how a Model-View-Presenter approach may be used in an ASP.NET WebForms project. Lots of what is out there on this subject seem to be rather fluffy in nature, bits and pieces, not really giving you everything that you need; especially if you have not worked with a MVP paradigm nor Inversion of Control before. I have built a concrete and fully compilable example Visual Studio 2010 solution using StructureMap for IoC, as well as AutoMoq and NUnit for unit testing and automatic mocking. To get your hands on the code, please see the bottom of this post. I realize that the flow in which this is written do not go well with the teachings of Test Driven Development (lucky for me, this post is not about TDD). Being a great fan of the paradigm though, I would recommend you to try it out if you have not done so yet.

Constructing this example solution I have drawn a great deal of inspiration from previously developed code; special thanks to my current, and former, collegues Patrik Akselsson, Karl Ahlin, Oskar Bejbom, Joakim Molin, Jonatan Larsson and Ilja Mikhailov.

MVP solution Visual Studio 2010 solution explorer, overview of the projects.

In order to make things easier to understand, I have divided the solution into four different projects; Generic.Bootstrap, Generic.Core, Generic.Test.Unit and Generic.Web. The Web project, together with Core, are holding the Model-View-Presenter files. Bootstrap is where most StructureMap related things happen, and Test.Unit is the home of NUnit, and AutoMoq. Whether you download the zipped source code archive or get the solution from GitHub, you might need seeing to a few dependencies. All these are listed in the package.config files since I use NuGet, but here they are nonetheless.

The Model-View-Presenter approach

So, why bother with Model-View-Presenter? Why not create your projects using ASP.NET MVC? Well, If you are starting a new project and have the opportunity, then MVC is probably the better choice. If you are stuck with an existing WebForms solution however, it will likely be easier to refactor it into MVP rather than converting your code into a Model-View-Controller project.

If you have the time to implement a MVP solution for your web forms, and your project seems large enough for you to reek the benefits, it is probably worth separating your code and not keep all logic in the code-behind files of your pages and user controls.

MVP solution Visual Studio 2010 solution explorer, Generic.Core.MVP solution Visual Studio 2010 solution explorer, Generic.Web.

There are relatively many files in the Core and Web projects; we will go through them and what they are used for one by one. First, let us look at the PresenterBase class.

PresenterBase.cs

public abstract class PresenterBase
{
    public virtual void Load() {}
    public virtual void PreRender() {}
    public virtual void FirstTimeInit() {}
}

This will be the base of all of our presenters. We will use it to have presenter implementations of the three methods run at the proper time in the life cycle; the Load (5) will be run during OnLoad for instance. It will all be clearer in a while. Next the IView interface.

IView.cs

public interface IView
{
    Uri Uri { get; }
}

This is our base view interface, which all of our other interfaces will be extending. If there are anything that you want available everywhere, this might be the place to put it. Since the example solution does not differ so much in how the PresentedPageBase and the PresentedControlBase are implemented, I have decided only to talk about one of them; the PresentedPageBase.

PresentedPageBase.cs

public abstract class PresentedPageBase<TPresenter> : Page, IView where TPresenter : PresenterBase
{
  private TPresenter _presenter;

  protected override void OnLoad(EventArgs e)
  {
    base.OnLoad(e);
    _presenter = CreatePresenter();

    if (!IsPostBack)
    {
      _presenter.FirstTimeInit();
    }

    _presenter.Load();
  }
  protected TPresenter CreatePresenter()
  {
    return IOC.GetPresenter<TPresenter>(this);
  }

As you can see, the PresentedPageBase class (8) requires a presenter inheriting from the PresenterBase which we just looked at. In the OnLoad event, the generic type presenter is created using Inversion of Control (34); we will come to this in a bit. We then fire the presenter’s implementation of FirstTimeInit if the page is being requested for the first time, i.e. it is not a PostBack event. The presenter’s Load and PreRender implementations are handled in the same way. As the PresentedPageBase implements our IView interface we might want to implement things we have put there; the Uri property in the example code above simply returns the HttpContext.Current.Request.Url.

So, now that we have got some infrastructure done it is time to start looking at how to use it; the start page is our example for this. It uses the Default.aspx and Default.aspx.cs files, as well as the StartPagePresenter class and the IStartPageView interface.

IStartPageView.cs

public interface IStartPageView : IView
{
    string ImportantHeading { get; set; }
    string ImportantText { get; set; }
}

As mentioned before this interface will extend the base IView interface, and then add components of its own; these may be important properties as in the example (5-6), methods, event handlers and so on. Just about anything that you need to stuff in there.

StartPagePresenter.cs

public class StartPagePresenter :  PresenterBase
{
  private readonly IStartPageView _view;
  private readonly IImportantService _importantService;
public override void Load()
{
  _view.ImportantHeading = GetHeading(true);
  _view.ImportantText = GetText(true);
}

public string GetHeading(bool useService)
{
  return useService ?
    _importantService.GetImportantHeading("world") :
    "No heading";
}

Extending the PresenterBase class in our StartPagePresenter (6) will give us the important Load (19) and PreRender methods. While the Dependency Injection will be used through the presenter’s constructor (see the StructureMap section of this post), we set the values of our view’s important properties during the Load event in the page life cycle (21-22). As you can see, the GetHeading method uses another class, ImportantService (28), to retrieve a value; this is done to provide an easy-to-understand unit testing example further on. However, it is important not to let your presenters swell into uncomprehensable monsters. Doing so will leave you with massive pieces of unmaintainable code. As one of my collegues told me at the time that I started working with presenters: “If you need more than say five dependencies, your class is probably doing a lot more than it ought to be.” We will get back to this later.

Now that we have a presenter and a view for our start page, it is time to start looking at what the visitor will actually be seeing. The Default.aspx.cs file is where the interesting line of code is, but first a short code snippet from the code-infront just to show you the controls.

Default.aspx

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2><asp:Literal ID="litImportantHeading" runat="server"></asp:Literal></h2>
    <p><asp:Literal ID="litImportantText" runat="server"></asp:Literal></p>
    <p><uc:ControlText ID="ucControlText" runat="server" /></p>
</asp:Content>

Default.aspx.cs

public partial class Default : PresentedPageBase<StartPagePresenter>, IStartPageView
{
  public string ImportantHeading
  {
    get { return litImportantHeading.Text; }
    set { litImportantHeading.Text = value; }
  }

There is really not much to talk about here. The code-behind class extends the PresentedPageBase with the StartPagePresenter in place of the generic presenter. This together with the inheritance from the IStartPageView will tie the last Model-View-Presenter knot together. Implementing the ImportantHeading property in the example (10-11), we want the text to show up on the actual page readable by the visitor.

Building the solution and visiting the page now would give us a runtime exception letting us know that the PresentedPageBase _presenter object reference is not set to an instance of an object as it is hit with the first _presenter.FirstTimeInit() call. What is wrong? I thought we were done. Right, IoC using StructureMap.

Inversion of Control using StructureMap

Dependency injection is the glue that ties all this together. I have put what is needed for this functionality in the separate Generic.Bootstrap project as you can see in the image below. Of course there are a lot more things that you can do with StructureMap than what is shown in this example, but this is really all you need for it to wire up your presenters and views.

MVP solution Visual Studio 2010 solution explorer, Generic.Bootstrap.

Let us start by looking at the Initializer class. This is the class that is responsible for setting the whole thing in motion. As you can see in the code snippet, it implements the interface IHttpModule, meaning it will be run at start up. All it really does is creating a new Bootstrapper (10) and having it trigger StructureMap (11). We will get back to the assignment of the IOC.Container property in a moment. One more thing before we move on to the Bootstrapper class; in order for the Initializer module to run, it must first be registered in the system.webServer’s modules section of your web project’s web.config file.

Initializer.cs

public class Initializer : IHttpModule
{
    public void Init(HttpApplication context)
    {
        var bootstrapper = new Bootstrapper();
        bootstrapper.BootstrapStructureMap();
        IOC.Container = bootstrapper.Container;
    }

    public void Dispose()
    {
    }
}

Web.config

<system.webServer>
 <modules>
   <add name="StructureMapInitializer"
        type="Generic.Bootstrap.Initializer, Generic.Bootstrap" />
 </modules>
</system.webServer>

The Bootstrapper class implements StructureMap’s IBootstrapper interface (6) making you implement the BootstrapStructureMap method (10). What this should do is just what the name says it should; bootstrap StructureMap. In this example we use it to scan (17-21) through the assembly that contains the class Bootstrapper (actually this assembly), looking for registries (in other words, classes that extend StructureMap’s Registry class). All of this is baked together and stuffed into a main Container object (8, 12).

Bootstrapper.cs

public class Bootstrapper : IBootstrapper
{
    public IContainer Container;

    public void BootstrapStructureMap()
    {
        Container = new Container(AddRegistryInfo);
    }

    private static void AddRegistryInfo(IRegistry registry)
    {
        registry.Scan(scanner =>
        {
            scanner.AssemblyContainingType<Bootstrapper>();
            scanner.LookForRegistries();
        });
    }
}

So, assuming that everything went well, StructureMap should now have found our CoreRegistry class in the Registries directory. This is a class that can provide methods and configuration for an ObjectFactory or a Container as in our case. The documentation says that extending the Registry class is the recommended way of configuring a StructureMap Container. We use the registry scanner to scan through our code based on four parameters.

CoreRegistry.cs

public class CoreRegistry : Registry
{
  public CoreRegistry()
  {
    Scan(scanner =>
    {
      scanner.RegisterConcreteTypesAgainstTheFirstInterface();
      scanner.AssemblyContainingType<PresenterBase>();
      scanner.WithDefaultConventions();
      scanner.SingleImplementationsOfInterface();
    });
  }
}

This will make the scanner look through the assembly containing the PresenterBase class (in other words the Generic.Core project where we put all of our presenters) using default conventions (13-14). The latter part means that a concrete class named for instance ImportantService that implements an interface named IImportantService automatically will be added to PluginType IImportantService. RegisterConcreteTypesAgainstTheFirstInterface (12) does what it says it does, and the SingleImplementationsOfInterface (15) registers types that are the only implementation of an interface.

IOC.cs

public static IContainer Container;
public static TPresenter GetPresenter<TPresenter>(object view)
{
    var explicitArguments = new ExplicitArguments();
    foreach (var implementedInterface in view.GetType().GetInterfaces())
    {
        explicitArguments.Set(implementedInterface, view);
    }
    return Container.GetInstance<TPresenter>(explicitArguments);
}

One last thing before we can compile the solution and visit the website; we need to retrieve an instance of our presenter. This is done in the IOC class through the GetPresenter method as seen above.

MVP Visual Studio 2010 project, Visiting start page with FireFox.

Unit testing using NUnit and AutoMoq

The last section of this example post mostly circulates around the Generic.Test.Unit project. We will first look at the base classes, and then turn our attention to the StartPagePresenter tests.

MVP solution Visual Studio 2010 solution explorer, Generic.Test.Unit.

Let us begin by looking at the TestBase class; the base on which we will build many of our unit tests. It is used to do a lot of NUnit setup so we can avoid doing it over and over in every test class. The interesting things are the creation of the ClassUnderTest as well as the execution of the Given and When methods.

TestBase.cs

[TestFixture]
public abstract class TestBase<TClassUnderTest> where TClassUnderTest : class
{
  protected abstract TClassUnderTest ResolveClassUnderTest();

  private TClassUnderTest _classUnderTest;
  protected TClassUnderTest ClassUnderTest
  {
    get { return _classUnderTest ?? (_classUnderTest = ResolveClassUnderTest()); }
  }
[SetUp]
public void Init()
{
    _classUnderTest = null;

    Given();
    When();
}

protected virtual void Given()
{
}

As you can see, the ClassUnderTest property (13) calls the protected abstract method ResolveClassUnderTest (8) leaving it to each implementing test class to decide how this setup should be performed. The NUnit attribute SetUp is used to allow for every class under test to have their Given prerequisites as well as their When actions run in a logic order for all the tests extending the TestBase class. It is also possible to add things that should happen after each tests is performed. Since it is a lot of work setting up everything for a test to work, the AutoMockedTestBase class is created to take some of the load off of our shoulders.

AutoMockedTestBase.cs

public abstract class AutoMockedTestBase<TClassUnderTest> : TestBase<TClassUnderTest>
  where TClassUnderTest : class
{
  protected AutoMoqer Container;

  protected override TClassUnderTest ResolveClassUnderTest()
  {
    return Container.Resolve<TClassUnderTest>();
  }
  protected override void Given()
  {
    Container = new AutoMoqer();
    base.Given();
  }
}

AutoMoq is used to automatically setup Moq mocking for our unit tests. Just like the TestBase which it is extending, the AutoMockedTestBase requires the developer to supply a generic class which is supposed to be tested. To avoid having to decide how to create a ClassUnderTest AutoMoq does this for us by resolving it automatically putting it in a mocking container. Basically, Container is set up to hold the class which we are testing, as well as instances of all of its dependencies; in other words, the StartPagePresenterSpecification container would hold instances of both the StartPagePresenter as well as the ImportantService on which it depends.

StartPagePresenterSpecification.cs

public class StartPagePresenterSpecification : AutoMockedTestBase<StartPagePresenter>
{
  protected override void Given()
  {
    base.Given();
    Using<IImportantService>()
      .Setup(s => s.GetImportantHeading("world"))
      .Returns("FakeHeading");

    Using<IImportantService>()
      .Setup(s => s.GetImportantText("WORLD"))
      .Returns("FakeText");
  }

  [Test]
  public void should_return_service_heading_result_when_using_service()
  {
    Assert.AreEqual("FakeHeading", ClassUnderTest.GetHeading(true));
  }

The StartPagePresenterSpecification makes use of the automatic mocking which we just set up (8), and continues with setting up the test prerequisites overriding the Given method. If you have not worked with this kind of testing before, it is rather straight forward; Given contains all of your setup for getting the system in the state you wish it to have at the beginning of your tesets, When performs the actions which will cause your system to respond, and the methods decorated with the Test attribute asserts that your actions on the system had the desired outcome. In the example test above I have omitted the use of When and put the call to the GetHeading method directly into the test method instead.

MVP solution Visual Studio 2010 solution explorer, Running unit tests from the context menu.

Running the unit tests from the right mouse-click context menu will build your projects and then run the selected tests. The Unit Test Sessions window will let you know if a test failed, and why it decided to do so.

GenericProject ContextMenu Unit Test Session With Failing Test  Complete and concrete example of what an ASP.NET WebForms Model-View-Presenter project may look like using StructureMap, NUnit and AutoMoq

Source code

The project is available as a zipped archive and is fully functional, but I would recommend you using the GitHub version as it may contain updates that the zip does not.

Source code: VisualStudioMVPProject_src_1.0.0.0.zip
GitHub link: https://github.com/matkun/Blog/tree/master/ExampleMVPVisualStudio2010Solution

Posted in ASP.Net | Tagged AutoMoq, C#, Model-View-Presenter, Moq, MVP, NUnit, StructureMap | 1 Comment

Internet Explorer loses Referrer when redirecting or linking with JavaScript

I came across another Internet Explorer feature today as I was debugging a piece of code related to adding referrer URLs when submitting input forms. It worked perfectly on all versions that I tried of Firefox and Chrome, the referring URL was there, but when it came to IE it just would not give me the address. As the failing incoming link was being syndicated to other sites via an XML feed, and everything looked just fine for our part, the issue had to be at the receiving end.

After some Fiddling and FireBugging it turned out that the failing website was handling the links in a rather unusual way.

<a title="Link that opens in a new window" onclick="window.open(this.href,'_blank'); return false;" href="http://www.something.se/SubmitComments/">Submit comments</a>

As soon as I removed the onclick attribute using FireBug and replaced it with a target=”_blank”, the link suddenly started to work just the way that I expected it to. Apparently Internet Explorer considers window.open to be a new request, much in the same manner as when the visitor types the URL into the browser themselves. Chrome and FireFox do not.

window.location = "http://www.something.se/SubmitComments/";
document.location = "http://www.something.se/SubmitComments/";

Redirecting using window.location or document.location result in the same thing; no referrer using IE. I am not sure why this particular website decided to render the links in this way, but I hope they had some good reason to. If you really need to use JavaScript to redirect your visitors and want to keep the referring address, you would have to get involved with some rather bad looking hacks; like the following.

function redirectForIE(targetUrl) {
  var link = document.createElement('a');
  link.href = targetUrl;
  document.body.appendChild(link);
  link.click();
}

function redirectForTheRest(targetUrl) {
  location.href = targetUrl;
}
Posted in ASP.Net, Bugs | Tagged HTML, Internet Explorer, JavaScript | Leave a comment

Supplying EPiServer scheduled jobs with parameters through Admin Mode

I have always missed a way of supplying arbitrary input parameters to EPiServer scheduled jobs through the Admin Mode scheduled job interface. As Stefan Forsberg laughingly pointed out to me when I told him about my POC for this functionality (something in the lines of “Oh, I see you have found yourself a hammer.”), I seem to have found a way of using control adapters for just a bit of everything lately. The source code including a sample job is found at the bottom of this post.

The interface of the EPiServer scheduled parameter job

As shown in the image below, the code adds a fieldset to the Settings tab of the scheduled job containing arbitrary input controls. All except the Save values and the Reset values buttons are defined by the developer in advance. Naturally, the standard EPiServer scheduling controls continue to function in the expected manner.

ParameterJob SampleParameters  Supplying EPiServer scheduled jobs with parameters through Admin Mode

After clicking the Save values button, a message is presented to the administrator letting them know if everything was saved successfully. The same goes of course for the Reset values button.

ParameterJob SampleReset  Supplying EPiServer scheduled jobs with parameters through Admin Mode

If we were to manually run the scheduled job before resetting the parameters above, or have the EPiServer scheduler do it, the Sample parameter job would display all of the inputs in the Message column of the History tab, showing how it can be done.

ParameterJob SampleHistory  Supplying EPiServer scheduled jobs with parameters through Admin Mode

Defining input parameters for an EPiServer scheduled job from codebehind

As I mentioned earlier, this solution relies on a control adapter for it to work. By changing the EPiServer.UI.Admin.DatabaseJob it is possible to add rendering as well as paramerter persisting functionality to the EPiServer job. This is done by adding an adapter node to a browser file in your project’s App_Browsers directory, followed by inheriting an adapter class; in this case PageAdapter. See this post if you are curious about how it works.

AdapterMappings.browser

<browsers>
 <browser refID="Default">
  <controlAdapters>
   <adapter controlType="EPiServer.UI.Admin.DatabaseJob"
            adapterType="EPiServer.. ..DatabaseJobAdapter" />
  </controlAdapters>
 </browser>
</browsers>

The DatabaseJobAdapter and the scheduled job itself are linked together using an extension of the EPiServer ScheduledPlugInAttribute together with persisting values through the DynamicDataStore. In other words, by using the new (11) attribute’s DefinitionsClass (14) and DefinitionsAssembly (15) properties it is possible to target a class containing a skeleton setup of the input controls.

SampleParameterJob.cs

[ScheduledPlugInWithParameters(
    DisplayName = "Sample parameter job",
    Description = "Sample job with parameters",
    DefinitionsClass = "EPiServer .CodeSample.ScheduledJobs.ParameterJob.DefinitionSample",
    DefinitionsAssembly = "EPiServer.Templates.AlloyTech"
)]
public class SampleParameterJob : ScheduledJob
{
    public static string Execute()
    {

The targeted definitions class must implement the IParameterDefinitions interface in order to be used. This will make sure that all of the job parameter requirements are fulfilled. The GetParameterControls (8) method is what should be returning the data transfer objects for each input control; hard coded or generated. ParameterControlDTO is just a wrapper to help transferring a Control together with an optional label and a tooltip description.

IParameterDefinitions.cs

public interface IParameterDefinitions
{
    IEnumerable<ParameterControlDTO> GetParameterControls();
    void SetValue(Control control, object value);
    object GetValue(Control control);
}

The GetValue (10) and SetValue (9) methods are needed to let the adapter know how it should go about persisting and loading its values. Since many controls handle their values in different ways, the developer needs to supply specifications on the controls that they are using; more on this in the “What do I have to do to get input fields in my own scheduled job then?” section further down.

When clicking on a scheduled job in the left hand Admin Mode submenu, a pluginId query parameter is included in the request. This is what tells EPiServer which job it should be looking at. By using this to get hold of a PlugInDescriptor (37) it is possible to determine whether or not the scheduled job should be getting input parameters; if the _attribute variable is null, the job is not using the ScheduledPlugInWithParametersAttribute (38).

DatabaseJobAdapter.cs

var descriptor = PlugInDescriptor.Load(int.Parse(PluginId));
_attribute = descriptor.GetAttribute(typeof (ScheduledPlugInWithParametersAttribute)) as ScheduledPlugInWithParametersAttribute;

Once we have gotten hold of the scheduled job’s attribute, it is just a question of using its DefinitionsAssembly and DefinitionsClass properties to instantiate the skeleton class (51-52).

var assembly = Assembly.Load(Attribute.DefinitionsAssembly);
_parameterDefinitions = assembly.CreateInstance(Attribute.DefinitionsClass) as IParameterDefinitions;
if (_parameterDefinitions == null)
{
    throw new Exception("Your DefinitionsClass must implement the IParameterDefinitions interface.");
}

The DatabaseJobAdapter uses OnInit to loop through the ParameterControlDTO objects that it receives from the GetParameterControls method; rendering EPiServer Admin Mode like HTML for each one as it goes. Should there exist a persisted value for any of the controls, it will be applied instead of the default one coming from the skeleton class. This is where the previously mentioned GetValue and SetValue methods come into play.

The input parameter values are stored using EPiServer’s DynamicDataStore with the scheduled job’s pluginId as an identifier. When the Save values button is pushed in the interface, the click event handler uses a store extension together with the GetValue method to save everything to the database. The same goes for loading the persisted values when rendering the controls, or actually running the scheduled job.

DynamicDataStoreExtensions.cs

public static void PersistValuesFor(this DynamicDataStore store, string pluginId, IEnumerable<Control> controls, Func<Control, object> controlvalue)
{
    store.Save(
        new ScheduledJobParameters
        {
            PluginId = pluginId,
            PersistedValues = controls
                .ToDictionary(c => c.ID, controlvalue)
        });
}

public static Dictionary<string, object> LoadPersistedValuesFor(this DynamicDataStore store, string pluginId)
{
    var parameters = store.LoadAll<ScheduledJobParameters>() .SingleOrDefault(p => p.PluginId == pluginId);
    return parameters != null ? parameters.PersistedValues : new Dictionary<string, object>();
}

The Reset values button works in a similar fashion. It removes all occurences of data with the current pluginId from the DynamicDataStore.

“What do I have to do to get input fields in my own scheduled job then?”

In short, you will have to see to two things; the definitions class, and your scheduled job. First, create a class and make it implement the IParameterDefinitions interface. The DefinitionSample.cs file included in the source code archive is doing just this (12).

DefinitionSample.cs

public class DefinitionSample : IParameterDefinitions
{
  public IEnumerable<ParameterControlDTO> GetParameterControls()
  {
    return new List<ParameterControlDTO>
                {
                  AddACheckBoxSample(),
                  AddATextBoxSample(),
                  AddAnInputPageReferenceSample(),
                  AddACalendarSample(),
                  AddADropDownListSample()
                };
  }

The GetParameterControls is where you generate a List of whatever is up your alley when it comes to input controls. The sample definitions are adding a few example controls to show how it may be done. For instance, granting the web administrator the ability to select a page from the EPiServer PageTree (20) may be highly appreciated.

private static ParameterControlDTO AddAnInputPageReferenceSample()
{
  return new ParameterControlDTO
   {
     LabelText = "InputPageReference Sample",
     Description = "Sample of an EPiServer Page Selector control; InputPageReference.",
     Control = new InputPageReference { ID = "InputPageReferenceSample" }
   };
}

The sample InputPageReference control does not really do much, it just states that there will be one with a certain ID (105). It is up to you to add useful controls to the job, and they no not necessarily need to be hard coded. Adding a DropDownList for instance, you would probably want to populate it with ListItem objects dynamically. If the LabelText property is omitted, different HTML will be rendered for the control in the scheduled job interface; see for instance the image of the CheckBoxSample control at the beginning of this post.

What the SetValue and GetValue methods will contain depends on what kind of parameters you added to your control definitions. If you added an InputPageReference control and a CheckBox you would have to match the control to the proper type and handle the value accordingly.

public void SetValue(Control control, object value)
{
  if (control is CheckBox)
  {
    ((CheckBox) control).Checked = (bool) value;
  }
..
  else if (control is InputPageReference)
  {
    ((InputPageReference) control).PageLink = (PageReference) value;
  }

The same, somewhat ugly, mapping is necessary when retrieving a value.

public object GetValue(Control control)
{
  if (control is CheckBox)
  {
    return ((CheckBox) control).Checked;
  }
..
  if (control is InputPageReference)
  {
    return ((InputPageReference) control).PageLink;
  }

When all that work is done, it is time to create the scheduled job itself. Make sure that you inherit from EPiServer’s ScheduledJob class (17), but rather than using the normal ScheduledPlugIn attribute, use the ScheduledPlugInWithParameters one (11). Point the DefinitionsClass (14) and DefinitionsAssembly (15) properties to where your definitions class is located and create the usual static Execute method (19).

SampleParameterJob.cs

[ScheduledPlugInWithParameters(
    DisplayName = "Sample parameter job",
    Description = "Sample job with parameters",
    DefinitionsClass = "EPiServer .CodeSample.ScheduledJobs.ParameterJob.DefinitionSample",
    DefinitionsAssembly = "EPiServer.Templates.AlloyTech"
)]
public class SampleParameterJob : ScheduledJob
{
    public static string Execute()
    {
        var descriptor = PlugInDescriptor.Load("EPiServer.CodeSample .ScheduledJobs.ParameterJob.SampleParameterJob", "EPiServer.Templates.AlloyTech");
        var store = typeof (ScheduledJobParameters).GetStore();
        var parameters = store.LoadPersistedValuesFor( descriptor.ID.ToString(CultureInfo.InvariantCulture));

In the Execute method (19), a PlugInDescriptor (21) may be used to retrieve the proper parameter values from the DynamicDataStore (23) via the pluginId; I have not really found a better way of getting hold of this id, but it really ought to be possible. Will have to read the documentation one of these days. As the values are located in a Dictionary with your control IDs as keys, it should be an easy task for you to retrieve them and cast them to their proper types.

var cbChecked = parameters.ContainsKey("CheckBoxSample") && (bool) parameters["CheckBoxSample"] ? "Aye!" : "Nay..";
var sampleReference = parameters.ContainsKey("InputPageReferenceSample") ? (PageReference)parameters["InputPageReferenceSample"] : PageReference.EmptyReference;
var samplePageName = sampleReference != null && sampleReference != PageReference.EmptyReference ? DataFactory.Instance.GetPage(sampleReference).PageName : string.Empty;
var result = string.Empty;
result += string.Format("CheckBoxSample checked: <b>{0}</b><br />", cbChecked);
result += string.Format("InputPageReferenceSample page name: <b>{0}</b> (PageId: <b>{1}</b>)<br />", samplePageName, sampleReference);

You cannot rely on the Dictionary‘s ContainsKey method to be sure that a value is set; if a web administrator clicks the Reset values button and then saves the empty fields hitting Save values, empty strings and null values will be persisted in the DynamicDataStore.

Troubleshooting the EPiServer scheduled parameter job

Input parameter controls do not show up in the EPiServer scheduled job interface

The most common reason for the input parameter controls not to show up in the EPiServer scheduled job interface, even though the scheduled job class has the ScheduledPlugInWithParameters attribute, is that the attribute’s properties are wrong. More exactly, either the DefinitionsClass or the DefinitionsAssembly is not pointing to where it should be.

Troubleshooting the scheduled parameter job, Definition sample class.

Above is the definitions class from the example included in the source code when it is used in the EPiServer AlloyTech sample project in a directory called ScheduledJobs. The DefinitionsClass property of the attribute should in this case have the value EPiServer.ScheduledJobs.DefinitionSample.

Troubleshooting the scheduled parameter job, showing the bin directory.

The assembly file that is created by the EPiServer AlloyTech sample project is called EPiServer.Templates.AlloyTech.dll as seen in the image above; meaning the value of DefinitionsAssembly should be EPiServer.Templates.AlloyTech.

Troubleshooting the scheduled parameter job, Sample parameter job class.

Saved input values are not being used by the EPiServer scheduled job

If the values that you have entered in the input parameter controls are persisted when you click the Save values button, but your EPiServer scheduled job still refuses to access them, the problem is very likely to be in the loading of the PlugInDescriptor. If you do not know the plugin id, you will need to supply the Load method with two parameters; the namespace and scheduled job class, as well as the assembly in which the job resides.

Troubleshooting ParamJob PlugInDescriptor Load  Supplying EPiServer scheduled jobs with parameters through Admin Mode

The image above shows the EPiServer scheduled sample parameter job from the example in the source code when it is being used in the EPiServer AlloyTech sample project. As you can see, the class name and namespace part is set to EPiServer.ScheduledJobs.SampleParameterJob and the assembly is EPiServer.Templates.AlloyTech.

Source code

The binary version and the NuGet package do not include the example job; for this you would have to get the source code zip, or have a look at GitHub.

Source code: ScheduledParameterJob_src_1.1.0.0.zip (Latest)
Droppable binary: ScheduledParameterJob_binary_1.1.0.0.zip (Latest)
GitHub link: https://github.com/matkun/Blog/tree/master/ScheduledParameterJob
NuGet package: ScheduledParameterJob at the EPiServer NuGet feed.

Posted in ASP.Net, EPiServer, EPiServer CMS 6 R2, Scheduled jobs | Tagged automation, C#, Control Adapter, EPiServer, Scheduled job | Leave a comment

Allowing web editors to apply PageType based filtering on the EPiServer edit mode PageTree

When you have an EPiServer installation containing thousands of different pages built up by far-too-many page types, locating pages of just one of them may turn out to be just a little too time consuming. Since I have grown to like the adaptive control approach more and more lately, I decided to create a filtering mechanism for cleaning out dead ends in the EPiServer page tree based on this notion. Source code, droppable binaries, GitHub links and whatnot is found at the bottom.

Note: I did some refactoring after a review session with Stefan Forsberg (Thanks Stefan!), so the snippets in this post may not be entirely accurate. The overall paradigm is the same however. The 90′s also wanted its source code zip back, so I setteled for the droppable binary, GitHub and a NuGet package.

The page tree filter control showing all pages in the EPiServer edit mode page treeThe page tree filter control showing only pages of standard page type in the EPiServer edit mode page tree

The code adds a new DropDownList control at the top of the page tree, which in turn is populated with all of the available EPiServer page types in an index based order. Selecting one of them triggers a postback which rebinds the tree removing all unwanted pages. As you can see in the image above, some pages are bold and some are not; this is to help the user separating the actually selected pages from the ones just being above them in the tree keeping the structure intact. In other words, selecting the page type [AlloyTech] Standard page will give both the bolded standard page Resellers as well as the parent [AlloyTech] Listing page How to buy, and so on all the way up to the Root folder.

As you may suspect, the bold EPiServer shortcut pages in the image are both of the selected type; their boldiness has nothing to do with where the shortcut is in fact pointing. Since this is an adaptive control based solution, and control adapters tend to affect all instances of an original control, this filtering functionality will also be available for properties employing a page selector popup window; such as a PropertyLinkCollection or a PropertyPageReference. Furthermore, if you ever use the Favorites tab, you will find that the filter works here as well.

EPiServer PageTree PageType filter applied on a page selector popup windowEPiServer PageTree PageType filter being applied to the Favorites tab in edit mode

How control adapters help with filtering pages in the EPiServer PageTree

The filter functionality consists of two different control adapters; one responsible for adding the PageType selector drop down, the other performing the actual filtering. Use of these may be configured in the App_Browser directory; this post contains more information about configuring the usage of control adapters. However, unless you are doing something special it will probably be enough just to use the included mappings from the browser file inside the zip archive.

AdapterMappings.browser

<browsers>
 <browser refID="Default">
  <controlAdapters>
   <adapter controlType="EPiServer.UI.Edit.PageExplorer"
            adapterType="EPi.. ..Tree.PageExplorerAdapter" />
   <adapter controlType="EPiServer.UI.WebControls.PageTreeView"
            adapterType="EPi.. ..Tree.PageTreeViewAdapter" />
  </controlAdapters>
 </browser>
</browsers>

The PageExplorerAdapter is where the DropDownList is added; or rather the PageTypeSelector control inheriting the DropDownList class. In order for the code to be less cluttered I decided not to place the logic retrieving page types within the adapter itself, but rather make a separate control for it.

PageTypeSelector.cs

private static IEnumerable<ListItem> AllPageTypes()
{
    return PageType.List()
        .Select(pageType => new ListItem
        {
            Text = pageType.Name,
            Value = pageType.ID.ToString(CultureInfo.InvariantCulture)
        });
}

The default constructor of this control retrieves a list of all available EPiServer page types using EPiServer.DataAbstraction.PageType (24). These are mapped into more usable ListItem objects (25-29) and then attached as a DataSource to the DropDownList. Since the PageTypeSelector control is added inside of the OnInit event in the PageExplorerAdapter there is no need bothering with maintaining view or control states, as .NET will do this automatically later in the page lifecycle. For the code to be more pluggable, the CSS file link is inserted into the page header from this adapter as well; you might want to change this adding it in some more minify friendly fashion though.

The other adapter, the PageTreeViewAdapter, is where the more interesting things take place. An override of the OnLoad event allows for attaching of two new event handlers to the tree.

PageTreeViewAdapter.cs

((PageDataSource)pageTreeView.DataSource).Filter +=
    new FilterEventHandler(PageExplorerAdapter_Filter);
pageTreeView.PageTreeViewItemDataBound +=
    new PageTreeViewEventHandler(BoldifySelectedPageType_OnItemDataBound);
pageTreeView.DataBind();

The latter event handler, BoldifySelectedPageType_OnItemDataBound (61), is responsible for making the page names bold in the page tree for the proper pages. It does so simply by adding an additional CSS class to the correct link whenever a suitable page comes along. To filter the collection containing the pages, the event handler PageExplorerAdapter_Filter is attached to the PageTreeView‘s PageDataSource Filter event (59). If the event handler decides that the collection should indeed be filtered, it does so by looping through the pages removing all whose ids are not in a predetermined int array (called PagesToKeep).

 var pagesOfSelectedType = DataFactory.Instance.FindPagesWithCriteria( PageReference.RootPage, criteria);

    var parents = pagesOfSelectedType.Aggregate(Enumerable.Empty<int>(),
        (current, page) => current.Union( DataFactory.Instance.GetParents(page.PageLink).Select(r=>r.ID)))
        .ToList();
    parents.Add(PageReference.RootPage.ID);

    return parents.Union(pagesOfSelectedType.Select(p=>p.PageLink.ID));
}

The PagesToKeep array is constructed in the private PathsToSelectedPages method further down in the adapter class. First a criterion is set up and a basic FindPagesWithCriteria call is used to retrieve all pages with the correct PageTypeID (108). This is followed by a union (115) with a distinct list containing all of the page’s parents (110-113), in order to maintain the PageTree structure all the way to the root node.

The tricky part writing this filtering functionality was determining which PageType that the user wanted to filter on, as there may be multiple instances of the PageExplorer and PageTreeView controls on the same page; as for instance the ones residing on the Structure and the Favorites tabs. To solve this I wrote the two short control extension methods located in the ControlExtensions.cs file.

_selectedPageType =
    ((PageTypeSelector)CurrentPageExplorer. FindControlRecursively("PageTypeSelector"))
    .SelectedValue;
private PageExplorer _currentPageExplorer;
private PageExplorer CurrentPageExplorer
{
    get { return _currentPageExplorer ?? (_currentPageExplorer = Control.FindParentControlOfType<PageExplorer>()); }
}

As I knew that the PageExplorer control was a parent to the PageTreeView, and that I had placed the PageTypeSelector drop down within the PageExplorerAdapter, I decided to start from the inside going upward rather than trying to find the correct needle in the haystack from another direction.

The first extension method recursively looks for parent controls of a given type (35). In this case I wanted to find the closest PageExplorer control as it was the one being connected to the tree that I wanted to filter. After finding it I would recursively take myself back down again until I found the PageTypeSelector that I was looking for (25). As all of the involved controls are placed in such close proximity to one another, I doubt that the recursion will have any noticable effect on performance as opposed to trying to find the control by an id starting from somewhere else.

I have tried this at work in the project that I am currently working on. It has a giant code base and consists of approximately 22 000 EPiServer pages made up by 77 different page types. I thought it might cause a problem, but I was unable to see any noticable performance loss adding this functionality. It would be really interesting to test this on an even larger page quantity, and find out when I would need to implement some sort of caching.

Update: The PageTreeViewAdapter in Admin Mode

The Set Access Rights functionality in the EPiServer admin mode also uses the PageTreeView control to render its page tree. It does so without enclosing it inside a PageExplorer, and since the PageType filter assumes both controls to be present, as it uses the parent one for the PageType list, I decided to prevent it from being render here. Even though it may feel like a loss, I believe it to be a good thing as filtering could result in confusion for the user as they inherit access rights downward into the structure; it may not be totally clear to them which pages will have their rights changed if a filter is applied.

PageTreeFilter AdminMode SetAccessRights  Allowing web editors to apply PageType based filtering on the EPiServer edit mode PageTree

Update: New functionality

In version 1.1.3.0 new functionality have been added allowing user specific and global selection of the page types that should be available in the DropDownList. As the global information is persisted using the EPiServer Dynamic Data Store, it will not work for earlier versions of EPiServer CMS. You may read more about this functionality in this post.

Source code

The droppable binary version 1.1.2.0 is no longer the latest one, but it is the last one which do not make use of the EPiServer Dynamic Data Store.

Droppable binary: PageTypeTreeFilter_binary_1.1.2.0.zip
GitHub link: https://github.com/matkun/Blog/tree/master/PageTypeTreeFilter
NuGet package: PageTypeTreeFilter at the EPiServer NuGet feed.

Posted in ASP.Net, Control adapters, EPiServer | Tagged C#, Control Adapter, EPiServer, Extending edit mode, Extension methods, Filtering | 10 Comments