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:
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:
- 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.
- 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:
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:
- Sets the default view to our custom view.
- 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:
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.
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.
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.
This is great. As mark stated. A lot of the old CMS 6 Plugins can be moved to this view.
@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.
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
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...
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?
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/
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.
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.
@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.
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.
@Mark: Great that you found the cause!
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.
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.
Thanks for your reply. At least I know I can stop digging!
Hi Linus,
Do you have any example with MVC?
-TDzung
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;
}
Any clue why there is no name showing in the drop down?
Hello,
Regarding the MVC questions, I introduced the following base class whenever I want to use a custom (mvc) view.
Usage:
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