Linus Ekström
Apr 1, 2014
  12582
(3 votes)

Adding custom views to your content

My collegue Duong an Nguyen has already blogged about how to create custom views in EPiServer 7.5 here. But since I’m pretty sure that there are quite a few people that have either not read it or understood the power in this, so here is a recap. This is what we want to achieve, a new view for a given content type:

CustomViewScreenShot

In EPiServer 7.5, views like “On-Page Editing” and “All Properties” are configured using instances of a class named ViewConfiguration<T> where T is the type you want to add the view for. Let us create a simple view for the content type Product in the Alloy templates:

using System.Web;
using EPiServer.ServiceLocation;
using EPiServer.Shell;
using EPiServer.Templates.Alloy.Models.Pages;
 
namespace UIExtensions
{
    [ServiceConfiguration(typeof(EPiServer.Shell.ViewConfiguration))]
    public class SampleView : ViewConfiguration<ProductPage>
    {
        public SampleView()
        {
            Key = "sampleView";
            Name = "Sample View";
            Description = "A simple demo view";
            ControllerType = "epi-cms/widget/IFrameController"; 
            ViewType = VirtualPathUtility.ToAbsolute("~/UIExtensions/CustomView.aspx");
            IconClass = "customview";
        }
    }
}

As you can from the code above, there are two parts that defines how the view is loaded:

  1. ControllerType – Uses a built in JavaScript controller that loads a given view inside an IFrame. It's also responsible for reloading the inner view when the context is changed.
  2. ViewType – Defines the specifiv view to be loaded, in this case the URL to the page inside the iframe. In my sample, it points to a Web Forms based page, but it could as well be the URL to an MVC controller.

Now let us add the actual view. Since I want to be able to get a reference to the current content, I’ll use the base class ContentWebFormsBase. I create a new web forms view (The sample below does not use a code behind file) saved as “/UIExtensions/CustomView.aspx):

<%@ Page Language="C#" Inherits="EPiServer.Shell.WebForms.ContentWebFormsBase" %>
 
<asp:Content ContentPlaceHolderID="FullRegion" runat="server">
   <style>
       body{
           background-color: magenta;//Added just to highlight where the view resides
       } 
   </style>
   <h1>This is a sample view</h1>
 
    Name for current content: <EPiServer:Property runat="server" PropertyName="PageName" CustomTagName="h2" />
</asp:Content>

 

Also, I need to add a custom class in the style sheets loaded to the user interface to get my custom icon. In the Alloy templates, there is already a style sheet that is configured to be loaded when the user interface is loaded, so we add a new style to the “/ClientResources/Styles/Styles.css” file:

.Sleek .customview {
  background: url('../Images/CustomIconLarge.png') no-repeat;
  height: 24px;
  width: 24px;
}

 

We also save a new icon image that is 24x24 pixels to “/ClientResources/Images/CustomIconLarge.png”.

Now we are done. After compiling, the view appears for instances of ProductPage, and when we select it the view is loaded:

CustomViewScreenShot2

Controlling the views

You can also control the views a bit more, for instance which view is the default view as well as disabling views for specific content types. Let us add a UIDesciptor that does two things:

  1. Sets the default view to our custom view.
  2. Removes the “On-Page Editing view”.
using EPiServer.Shell;
 
namespace EPiServer.Templates.Alloy.Business.UIDescriptors
{
    [UIDescriptorRegistration]
    public class ProductPageUIDescriptor : UIDescriptor<EPiServer.Templates.Alloy.Models.Pages.ProductPage>
    {
        public ProductPageUIDescriptor()
        {
            DefaultView = "sampleView";
            AddDisabledView(CmsViewNames.OnPageEditView);
        }
    }
}

 

When we load an instance of the ProductPage type, we get the following:

CustomViewScreenShot3

With power comes great responsibility

I want to give you a word of advice. Adding new views like this is really powerfull, specifically with the combination of changing the default view. This should, however, be used with much causion. Adding new top level views makes the user interface more complex so it should only be used when the view is considered essential. Also, when adding a third view, the view switcher changes from a single button to a drop down menu, making things a bit more complex for the editors. We hope to be able to introduce the ability to add views to places that are less prominent, for instance in the tools drop down menu, in the future.

 

Apr 01, 2014

Comments

Apr 1, 2014 10:50 AM

Great!

One view I'd like to have is a more customized version of the "contentlisting" view where I could specify which columns I'd like to be displayed in the table.

Currently (at least last time I checked a couple of weeks ago) the columns were hard coded into the dojo widgets and templates.

Apr 1, 2014 12:05 PM

Awesome - so this is the equivalent of the old EditPanel GuiPlugin...... Nice to know there are options for extension points to the edit interface beyond building a dojo widget.

Apr 1, 2014 02:25 PM

This is great. As mark stated. A lot of the old CMS 6 Plugins can be moved to this view.

Apr 2, 2014 07:37 AM

@Alf: Yes, we considered that when we developed the custom views for Commerce in 7.5. However, there was need to add custom formatters to be able to display user friendly values which complicated things a bit. So we never got to the point of adding the posibility to configure the list. But I hope we can add this in the future with a bunch of formatters to configure)

@Mark/Jakob: Yes, this now makes it possible to port existing pre CMS 7 plug-ins. However, as mentioned in the blog post, one should think once of twice before "dumping" new views unto the editors. I also hope that we can make these views available in different places, for instance like in Duongs original blog post.

Anders Hattestad
Anders Hattestad Apr 3, 2014 12:00 PM

Hi

Do you have any example with MVC. Trying to get to my controller but
ContentRouteHelper instance = ServiceLocator.Current.GetInstance();
returns instance.Content==null, but the querystring is there...

A

May 16, 2014 08:51 AM

Hi Anders!

Your comment has not been forgotten, just that I haven't had time to give you a proper response yet. The short version is that there is no built in support for resolving the content item by the id query string in MVC. This is of course doable but you'd have to extract the content item from the query string in your controller right now. I plan to write some code that does this and post this here when I get time...

Mark Bagnall
Mark Bagnall Oct 27, 2014 03:05 PM

Linus,
I'm using a ControllerType = "epi-cms/widget/IFrameController" to display a 'legacy' .aspx page. The page loads fine, but if I click a button on the page, I get an AntiForgeryValidation error, is there a way of switching off the AntiForgery validation for my page in the i-frame, or indeed of supplying the token im my page?

Oct 27, 2014 03:19 PM

Hi Mark!

Did you inherit from the ContentWebFormsBase as described in this blog post?

http://world.episerver.com/Blogs/Linus-Ekstrom/Dates/2013/12/New-public-APIs/

Mark Bagnall
Mark Bagnall Nov 6, 2014 02:41 PM

Linus,
Yes - I've inherited from ContentWebFormsBase.
The button causing the issue is actually an EPiServer ToolButton defined in the EPiServer.UI dll
[Edit] It's actually any postback on the form. I have other 'legacy' forms which work fine, but I can't work out why this one should fail.

Mark Bagnall
Mark Bagnall Nov 10, 2014 11:30 AM

Linus, it seems to be a bug, as I can reproduce it on a standard Alloy demo site. I've raised an issue with developer Support for this.

Nov 10, 2014 02:17 PM

@Mark: Please send in your code with the support case. I tried reproducing this using the ToolButton posting to a simple event handler that just sets the text for a label and I see no problems when pressing the button.

Mark Bagnall
Mark Bagnall Nov 12, 2014 09:47 AM

Linus, turned out to be my fault: I'd overridden OnPreRender, but hadn't invoked base.PreRender().
This of course stopped the ContentWebFormsBase class from doing it's stuff.

Nov 12, 2014 09:48 AM

@Mark: Great that you found the cause!

Kenia Gonzalez
Kenia Gonzalez Apr 10, 2015 03:32 PM

Hi Linus! Thank you for yet a great post, it was exactly what i was looking for =)
I have a question though, is there a way to access the CurrentPage (of type ProductPage in your example) from the UIDescriptor, say if you wanted to hide the view when a certain property is not set? It feels unnecessary to have the dropdownmenu instead of the button to edit the page when the custom view is not going to be showing any data.

Apr 13, 2015 07:20 AM

Hi Kenia!

Unfortunately, that is not possible. The reason is that the available views are stored in a settings object when the entire edit view is created and not when each item is loaded. I've noted your request though, if we for some reason should change this in the future.

Kenia Gonzalez
Kenia Gonzalez Apr 14, 2015 09:07 AM

Thanks for your reply. At least I know I can stop digging!

Trí Lâm
Trí Lâm Sep 22, 2015 05:17 PM

Hi Linus,

Do you have any example with MVC?

-TDzung

Dec 11, 2015 08:22 AM

Dont really know why i can see the name in the drop down?

[ServiceConfiguration(typeof(ViewConfiguration))]
public class ObjectImporterViewConfiguration : ViewConfiguration
{
public ObjectImporterViewConfiguration() : base(typeof(ObjectListPageTypeModel))
{
Key = "objectImporter";
Name = "Object_Importer";
Description = "Object Importer Tool";
ControllerType = "epi-cms/widget/IFrameController";
ViewType = VirtualPathUtility.ToAbsolute("~/EpiCustomization/Pages/WebForm.aspx");
IconClass = "epi-iconObjectFolder";
SortOrder = int.MaxValue;
}

NoName

Any clue why there is no name showing in the drop down?

Bob Maes
Bob Maes Jun 27, 2019 08:57 AM

Hello,

Regarding the MVC questions, I introduced the following base class whenever I want to use a custom (mvc) view.

    // source: https://world.episerver.com/documentation/developer-guides/CMS/user-interface/views/Creating-a-component-for-a-Web-Form/
    public class CustomIFrameViewPageController<TPageData> : PageController<TPageData> where TPageData : PageData
    {
        protected override void Initialize(RequestContext requestContext)
        {
            base.Initialize(requestContext);

            EnsureNodeDataToken(requestContext);
        }

        /// <summary>
        /// Ensure proper data token so that default ContentDataModelBinder / ContentDataValueProvider can do the model binding
        /// </summary>
        /// <param name="requestContext"></param>
        private void EnsureNodeDataToken(RequestContext requestContext)
        {
            var contentId = requestContext.HttpContext.Request.QueryString["id"];

            if (string.IsNullOrWhiteSpace(contentId)) throw new Exception("No context (content id in querystring) found on current request.");

            var contentReference = ContentReference.Parse(contentId);
            requestContext.RouteData.DataTokens.Add(RoutingConstants.NodeKey, contentReference);
        }
    }

Usage:

    public class CommentsController : CustomIFrameViewPageController<BlogOverviewPage>
    {
        public ActionResult Index(BlogOverviewPage currentPage)
        {
            // currentPage is available
            return Content("dummy");
        }
    }

The code sets the appropriate datatoken to enable the default epi model binding. It's an initial version so not all scenario's are supported.

It's an older post but maybe it's helpfull to anyone.

bye,

bob

Please login to comment.
Latest blogs
Multiple Anonymous Carts created from external Head front fetching custom Api

Scenario and Problem Working in a custom headless architecture where a NextJs application hosted in Vercel consumes a custom API built in a...

David Ortiz | Oct 11, 2024

Content Search with Optimizely Graph

Optimizely Graph lets you fetch content and sync data from other Optimizely products. For content search, this lets you create custom search tools...

Dileep D | Oct 9, 2024 | Syndicated blog

Omnichannel Analytics Simplified – Optimizely Acquires Netspring

Recently, the news broke that Optimizely acquired Netspring, a warehouse-native analytics platform. I’ll admit, I hadn’t heard of Netspring before,...

Alex Harris - Perficient | Oct 9, 2024 | Syndicated blog

Problem with language file localization after upgrading to Optimizely CMS 12

Avoid common problems with xml file localization when upgrading from Optimizely CMS 11 to CMS 12.

Tomas Hensrud Gulla | Oct 9, 2024 | Syndicated blog