help.axcms.netAxinom Logo
Save Save Chapter Send Feedback

Extending AxPage and AxDocument

To extend AxPage or AxDocument you just subclass them and add your properties methods. Properies are mapped to the columns in AxPage / AxDocument tables.

What is that all about?

Pages and documents in AxCMS.net are quite generic. Using a page you can implement an article, an event announcement, FAQ, press information etc. Using documents it is possible to add media content to those entitites.

By default both pages and documents have properties that are used by CMS. However, if you are not satisfied with provided properties, you can add some new ones, let them edit in the CMS interface and then read and use them somehow in your site.

This chapter describes how to do it. The way of extending AxPage and AxDocument is identical for both classes, that is why it is enough to describe only one process. Below, is shown an example of extending an AxPage.

Quickstart

1. Extend the AxPage table with your new field.

1.1. Create new files C:\Projects\AxCMS_Sample\Resources_Sample\DB\Create\CMS\Page.sql and C:\Projects\AxCMS_Sample\Resources_Sample\DB\Create\Live\Page.sql. Replace C:\Projects and _Sample as appropriate for your project.

1.2. Put the following into the file (modify it as appropriate):

ALTER TABLE dbo.AxPage ADD EventTime datetime NULL
GO
1.3. Extend your create db script with the call to the new DB script created - open the file C:\Projects\AxCMS_Sample\Resources_Sample\Deploy\CMS\CreateDB.build and add the following:

<SqlScriptExecute ConnectionString="$(DB_OleConDB_LS)" Filename="$(AxCMS_Target)\Resources_$(AxCMS_BaseName)\DB\Create\Live\Page.sql" />

<SqlScriptExecute ConnectionString="$(DB_OleConDB_MS)" Filename="$(AxCMS_Target)\Resources_$(AxCMS_BaseName)\DB\Create\CMS\Page.sql" />

1.4. Re-run your create db script and the create application.

Now you have the field EventTime in the AxPage tables.

2. Create a subclass of AxPage class. If you have a special project for your business logic you'll put this class in there, otherwise just put it into your templates project.

[OverridesAxPage]
public class SamplePage: AxPage
{
private DateTime _eventTime;

[AxDbMapToField]
[UserDefinedUI("~/templates/Controls/EventTimeCustomEditor.as", null, "Additional properties")]
public DateTime EventTime
{
get
{
return _eventTime;
}
set
{
if (_eventTime!= value) SetDirty();
_eventTime= value;
}
}
}

Pay attention to the class attribute OverridesAxPage. If you omit this, the AxPageBaseAdapter will return you AxPage objects from any loading method and won't save the new fields in any saving method, even if you pass SamplePage to them.

WARNING! You may have only one class marked with OverridesAxPage attribute among all assemblies placed into the bin folder of your live web application.

Pay attention to the method attribute AxDbMapToField and to the call of SetDirty in the property setter. Both are required and part of AxCMS.net Persistence Framework. Note: they are not special to extending of AxPage, you can use any other attribute or possibility of the persistance framework.

The attribute UserDefinedUIAttribute allow you to pass a URL of your custom editor. This editor control will be instantiated by AxCMS.net and displayed among other page properties. See below about creating it.

3. You can Use the new class in the normal way.
3.1. To load some page:

int pageID = 153;
AxPageBaseAdapter adapter = new AxPageBaseAdapter(PageTypes.Page);
adapter.PageSystem = Systems.LiveSystem;   // or Systems.ManagementSystem
SamplePage page = (SamplePage)adapter.Load(pageID);

3.2. To create a new page:

SamplePage page = new SamplePage();
page.Name = "Ilwrath";
page.Details.Caption = "Pkunk";
//set other properties here...
Page.EventTime = new DateTime(2001, 11, 9);
AxPageBaseAdapter adapter = new AxPageBaseAdapter(PageTypes.Page);
adapter.PageSystem = Systems.LiveSystem; // or Systems.ManagementSystem
adapter.Save(page);

3.3. To update a page:

int pageID = 153;
AxPageBaseAdapter adapter = new AxPageBaseAdapter(PageTypes.Page);
adapter.PageSystem = Systems.LiveSystem; // or Systems.ManagementSystem
SamplePage page = (SamplePage)adapter.Load(pageID);
page.EventTime = DateTime.Now;
adapter.Save(page);

3.4. To delete a page:

int pageID = 153;
AxPageBaseAdapter adapter = new AxPageBaseAdapter(PageTypes.Page);
adapter.PageSystem = Systems.LiveSystem; // or Systems.ManagementSystem
SamplePage page = (SamplePage)adapter.Load(pageID);
adapter.Delete(page);

Developing a custom field editor

Custom field editor is a user defined control (ascx) that can be developed either in your templates project or in your extras project. There is only one requirement to that control: it must implement the following interface:

public interface IAxCustomFieldEditor
{
void BindObject(object obj);
void InitControl(UIMapping mapping);
void InitForObject(object obj);
bool IsValid(object obj);
void ReadObject(object obj);
}

The method InitControl will be called early in the page load process. You will get an instance of the UserDefinedUIAttribute (which inherits from UIMapping). This object has three interesting properties: PassThrough, PanelName and Aspect.

In the PassThrough you just get any object you could pass in the attribute declaration. You can use it for example to develop a reusable custom field editor and to configure it using the PassThrough object.

Defining PanelName it is possible to place editors into different panels in CMS interface, grouping them according to some logic.

Aspects are similar to System.Reflection.PropertyInfo - you can use them to read or write data into some property of an object. The difference is that an aspect isn't bound to some particular class - if you have an aspect for property "Text" you can use it for TextBox, Label or YourOwnClassImplementingText.

In the Aspect property of UserDefinedUIAttribute you will get the property to which this attribute belongs. It is recommended that you save this aspect in the view state for the future use in ReadObject and BindObject.

The method BindObject will be called every time the object property should be bound to the user interface provided by your control.

You will get an instance of your BL object as a parameter (in our example it will be SamplePage). If your control is not reusable you can just cast the obj parameter to SamplePage and read your custom property directly. If you want to reuse the control, use the aspect as shown in the sample below.

 

The method ReadObject will be called every time the object property should be updated from the user interface provided by your control.

You will get an instance of your BL object as a parameter (in our example it will be SamplePage). If your control is not reusable you can just cast the obj parameter to SamplePage and write into your custom property directly. If you want to reuse the control, use the aspect as shown in the sample below.

public partial class EventTimeCustomEditor : System.Web.UI.UserControl, IAxCustomFieldEditor
{
protected TextBox _text;
protected AxAspect _myAspect;

protected void Page_Load(object sender, EventArgs e)
{
}

protected override object SaveViewState()
{
return _myAspect;
}

protected override void LoadViewState(object savedState)
{
_myAspect = savedState as AxAspect;
}

public void InitControl(UIMapping mapping)
{
_myAspect = mapping.Aspect;
}

public void InitForObject(object obj)
{
}

public void BindObject(object obj)
{
if (_myAspect != null)
{
_text.Text = ((DateTime)_myAspect.ReadFrom(obj)).ToShortDateString();
}
else
{
_text.Text = "Aspect unknown";
}
}

public void ReadObject(object obj)
{
if (_myAspect != null)
{
_myAspect.WriteTo(obj, DateTime.parse(_text.Text));
}
}

public bool IsValid(object obj)
{
return true;
}
}

Overriding page and document name encoding

Since AxCMS.net 8.2 it is possible to override page and document name encoding, e.g. to use hyphens (-) instead of underscores (_).

To do that:

For AxDocument: override string EncodeFileName(string name) method in your override of AxDocument. Method accepts document name as parameter and returns encoded name. Base method returns AxEncodingHelper.AxEncodeFileName(name).

For AxPage, AxMailTemplate and AxNewsletter: override EncodeName method in your override of AxPage. Method should return encoded AxPageBase.Name. Base method returns AxEncodingHelper.AxEncodeFileName(this.Name);

You need to override this method in all four classes - AxPageBase, AxPage, AxNewsletter and AxMailTemplate.

Before you can use the overriden page or document name encoding, you have to change encoded names for existing pages and documents in both file system and database.

Following batch script renames files from using underscore (_) to use dash (-). Run it in publish folders of both MS and LS:

@echo off
for /f "tokens=*" %%a in ('dir /B *_*.aspx') do call :RENAME "%%a"
pause
:RENAME
set oldName=%~1
set newName1=%oldName:_=-%
set newName2=%newName1:-ver-=_ver_%
ren "%oldName%" "%newName2%"

This script can do the same renaming in database:

update AxPage set Name = replace(Name, '_', '-'), URL = replace(URL, '_', '-')

Custom actions on page/document insertion, update, deletion

Since AxCMS.net 9.2 it is possible to subscribe to events triggered by adapter on object insertion, update and deletion, to implement additional activities needed for your custom logic. To do that, implement IAxDbObjectEvents interface in your page/document override.

    [OverridesAxPage]
    public class PremiumSamplePage : AxPage, IAxDbObjectEvents
    {
        //...
        #region IAxDbObjectEvents Members
        public void OnAfterDelete()
        {
            new SamplePublishAction(this, "OnAfterDelete").Log();
        }
        public void OnAfterInsert()
        {
            new SamplePublishAction(this, "OnAfterInsert").Log();
        }
        public void OnAfterUpdate()
        {
            new SamplePublishAction(this, "OnAfterUpdate").Log();
        }
        public void OnBeforeDelete()
        {
            new SamplePublishAction(this, "OnBeforeDelete").Log();
        }
        public void OnBeforeInsert()
        {
            new SamplePublishAction(this, "OnBeforeInsert").Log();
        }
        public void OnBeforeUpdate()
        {
            new SamplePublishAction(this, "OnBeforeUpdate").Log();
        }
        #endregion
    }