Welcome to the Learning Center

The Guide | Knowledge Base | FAQ

Browse it all or refine your selection using the filters below on the left.

Extending XMod Pro - Part 1:

Learn how you can package up your own Custom Form Controls for XMod Pro.

By: Kelly Ford On: 10/13/2009

Link to this Article
https://dnndev.com/Learn/Guide/Article/Extending-XMod-Pro-Part-1

Now that XMod Pro (XMP) for DotNetNuke (DNN) has been out for a few months, I'm starting to get a few questions about leveraging it's extensibility. Thus far, I've been responding to requests directly and haven't yet created any documentation for wider distribution. This series will help rectify that. We'll start with the most popular extension – custom form controls.

Building custom form controls for XMod Pro (XMP) is a great way to implement your own custom functionality, encapsulate business rules, or fill a gap in XMP's existing functionality. Plus, if you've built a control you think others would benefit from, you can package it up and give it away or sell it. XMod has a thriving ecosystem of add-on developers and XMod Pro's growing customer base will provide additional opportunities to add some revenue to your bottom-line during these difficult economic times.

Before we begin, I am assuming you are fairly comfortable with creating server controls in ASP.NET. That is beyond the scope of this article and there are may sources on the internet and in printed form that you can turn to for guidance. Rather, this article is meant to provide you with the information needed to apply your server control building-knowledge to creating custom XMod Pro controls. Let's begin.

Primarily, we are dealing with building controls that participate in the data-binding process of XMP. If you want to use a control merely for display purposes (say a tab control), you don't need to go any further. You can use 3rd party controls or your own custom server controls in XMod Pro forms just by using the <Register> tag to register them with XMP. They cannot be used to add or edit data, though. For that, you'll need to implement XMP's IDataBoundControl interface.

IDataBoundControl

All controls that participate in the data-binding process of XMP must implement the IDataBoundControl interface. It is pretty simple and, as you might expect, contains properties and functions used by XMP to communicate with your control when loading and saving data.

The fully qualified namespace for the interface is KnowBetter.XModPro.Web.Controls.IDataBoundControl. To use it, you'll need to add a reference in your project to KnowBetter.XModPro.Web.Controls.dll.

The properties in the interface should be familiar to you since you find them on most all of XMP's built-in controls.

Public Interface IDataBoundControl
    Property DataField() As String
    Property DataType() As System.Data.DbType
    Property Nullable() As Boolean
    Sub SetValue(ByVal BoundValue As Object)
    Function GetValue() As Object
End Interface
  • DataField: This is the name of the column in the <SelectCommand> and <SubmitCommand> to which the control is bound.

  • DataType: This is the data type that should be used for the column. If the column in the database is an "int", the customer would put Int32 in here. XMP does not support all the System.Data.DbType values, but it covers the major ones (see the help file for details).

  • Nullable: If the column can contain a NULL value (i.e. DbNull in .NET), this property will be set to True but the end user. Your control will be responsible for handling nulls.

  • SetValue: This method is called by XMP when it is loading data from the <SelectCommand>. It will pass your control the value retrieved for DataField. This value may be DbNull and your control should react accordingly.

  • GetValue: When XMP is ready to send form control values to the <SubmitCommand>, it will call this function on your control. If your control has its Nullable property set to True, then you should return DbNull from this function if your control has what it considers to be an empty value – an empty string, for instance, in a text box.

As you can see, what you have to implement for the interface is quite simple – a couple of properties that describe the column to which the control is bound, and a Set and Get function to set and retrieve a value from your control.

Now let's look at how the interface is implemented.

Implementing IDataSourceControl

The sample code we'll be looking at isn't all that useful as a custom control per se. We're focusing on the interface implementation. So, let's look at building a custom TextBox control that automatically HTML encodes its value. Again, I'm assuming you can setup your own server control project in Visual Studio and reference the KnowBetter.XModPro.Web.Controls.dll.

Imports KnowBetter.XModPro.Web.Controls

Public Class HtmlEncodedTextBox
    Inherits System.Web.UI.WebControls.TextBox
    Implements IDataBoundControl
    ...
End Class

First (line 1), we Import the KnowBetter.XModPro.Web.Controls namespace which is where the interface and some other helper items reside.

Next, in line 3, we've given our control the clunky but descriptive name of "HtmlEncodedTextBox". For simplicity, we're not going to re-invent the TextBox. Instead we'll simply inherit from ASP.NET's TextBox control (line 4) and augment it.

Finally, in line 5, we declare we're implementing the IDataBoundControl interface. After typing this line and hitting ENTER, the stubs for the interface will be automatically inserted into your code:

    Public Property DataField() As String Implements KnowBetter.XModPro.Web.Controls.IDataBoundControl.DataField
        Get


        End Get
        Set(ByVal value As String)


        End Set
    End Property


    Public Property DataType() As System.Data.DbType Implements KnowBetter.XModPro.Web.Controls.IDataBoundControl.DataType
        Get


        End Get
        Set(ByVal value As System.Data.DbType)


        End Set
    End Property


    Public Function GetValue() As Object Implements KnowBetter.XModPro.Web.Controls.IDataBoundControl.GetValue


    End Function


    Public Property Nullable() As Boolean Implements KnowBetter.XModPro.Web.Controls.IDataBoundControl.Nullable
        Get


        End Get
        Set(ByVal value As Boolean)


        End Set
    End Property


    Public Sub SetValue(ByVal BoundValue As Object) Implements KnowBetter.XModPro.Web.Controls.IDataBoundControl.SetValue


    End Sub

Let's take each item one at a time:

    Private _DataField As String
    Public Property DataField() As String Implements IDataBoundControl.DataField
        Get
            Return _DataField
        End Get
        Set(ByVal value As String)
            _DataField = value
        End Set
    End Property

In line 1 we're declaring a private variable to hold the DataField value. Line 4 returns that value when the property is read. Line 7 sets the variable when the property is set. This follows the standard property declaration for most controls and you'll find the other property declarations follow the same pattern:

    Private _DataType As System.Data.DbType = System.Data.DbType.String
    Public Property DataType() As System.Data.DbType Implements IDataBoundControl.DataType
        Get
            Return _DataType
        End Get
        Set(ByVal value As System.Data.DbType)
            _DataType = value
        End Set
    End Property


    ' When True, empty Text returned as Null
    Private _Nullable As Boolean = False
    Public Property Nullable() As Boolean Implements IDataBoundControl.Nullable
        Get
            Return _Nullable
        End Get
        Set(ByVal Value As Boolean)
            _Nullable = Value
        End Set
    End Property

In the above code, notice in lines 1 and 12, we've set a default value for the _DataType and _Nullable variables. This is good practice – especially for the DataType property as it should always have a default data type. Nullable is not as important because Boolean variables always default to False anyway.

Next, let's move on to the GetValue() function. This is called when XMP needs to get the control's value so it can be sent to the <SubmitCommand>. How you implement this function depends on the nature of your control. For our purposes, we want to HTMLEncode the value of our textbox and send that to XMP:

    Public Function GetValue() As Object Implements IDataBoundControl.GetValue
        If Me.Nullable Then
            If MyBase.Text.Trim.Length = 0 Then
                Return System.DBNull.Value
            End If
        End If

        Return Page.Server.HtmlEncode(MyBase.Text)

    End Function

Line 2 is important for any control to implement. The control must be able to handle NULL values in some way, shape or form. So, we check to see if Nullable has been set to True (the "Me." isn't necessary but has been added for this tutorial to emphasize Nullable is a property of the control). If so, we return a DBNull value only if our control is empty (defined as no characters or only whitespace for this control, but it could be something else depending on your control). Finally, in line 8 we take our control's value and HTMLEncode it, returning that to XMP.

The SetValue() method is called by XMP after the <SelectCommand> has been executed. This could happen when editing a record – the most likely cause – but can also happen if the user wants to set default values for an AddForm.

    Public Sub SetValue(ByVal BoundValue As Object) Implements IDataBoundControl.SetValue
        If IsDBNull(BoundValue) Then
            MyBase.Text = String.Empty
        Else
            MyBase.Text = Page.Server.HtmlDecode(BoundValue)
        End If
    End Sub

XMP sends us BoundValue which is of type Object. The first things the control's code should do is test to see if that BoundValue is DBNull and set its value(s) to what it defines as empty. In the case of our control, we just set the Text property to String.Empty. If the BoundValue isn't DBNull, we can take appropriate action to set the control's property/properties. In this case, we want to display the decoded HTML in the textbox, so we set Text to HtmlDecode(BoundValue). In the real world you would probably want to also do more data type checking, bounds checking, etc.

That's it. The control is technically complete. Before compiling though, we should also account for un-expected errors.

Trapping and Reporting Errors

Since the control's properties can be dynamically bound to data at run-time, it's possible that data may violate the rules for expected values in those properties. While your control is encouraged to trap these errors wherever appropriate, there are two generic areas you'll most likely want to implement. These will help your control play nicely with XMP's system for reporting errors.

    Protected Overrides Sub OnDataBinding(ByVal e As System.EventArgs)

        Try

            MyBase.OnDataBinding(e)

        Catch ex As Exception

            RaiseBubbleEvent(Me, New ControlErrorEventArgs(ex))

        End Try

    End Sub


    Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)

        Try

            MyBase.OnPreRender(e)

        Catch ex As Exception

            RaiseBubbleEvent(Me, New ControlErrorEventArgs(ex))

        End Try

    End Sub

In each of these event Overrides, we're catching any un-expected errors from the base class event and bubbling them up the stack. It's important to pass the control as the source of the error (Me), create a new instance of the ControlErrorEventArgs class, and set that object's Exception property to the exception that was just thrown. In the example above, I'm taking advantage of ControlErrorEventArgs alternate constructor to quickly create the class and set the exception.

You can and should use the RaiseBubbleEvent method, pass your control as the source, and use the ControlErrorEventArgs when a significant error is encountered in your control that needs to be reported. This allows XMP to better handle the error and report it to the user. NOTE: The ControlErrorEventArgs may be expanded in the future to provide more granular control over reporting options.

Here's the full code:

Imports System
Imports System.Web.UI.WebControls
Imports KnowBetter.XModPro.Web.Controls


Public Class HtmlEncodedTextBox

    Inherits TextBox

    Implements IDataBoundControl


#Region "Properties"


    Private _DataField As String

    Public Property DataField() As String Implements IDataBoundControl.DataField

        Get

            Return _DataField

        End Get

        Set(ByVal value As String)

            _DataField = value

        End Set

    End Property


    Private _DataType As System.Data.DbType

    Public Property DataType() As System.Data.DbType Implements IDataBoundControl.DataType

        Get

            Return _DataType

        End Get

        Set(ByVal value As System.Data.DbType)

            _DataType = value

        End Set

    End Property


    ' When True, empty Text returned as Null

    Private _Nullable As Boolean = False

    Public Property Nullable() As Boolean Implements IDataBoundControl.Nullable

        Get

            Return _Nullable

        End Get

        Set(ByVal Value As Boolean)

            _Nullable = Value

        End Set

    End Property


#End Region



    Public Sub New()

        ' Default to 'string' data type

        _DataType = DbType.String

    End Sub



    Public Function GetValue() As Object Implements IDataBoundControl.GetValue

        If Me.Nullable Then

            If MyBase.Text.Trim.Length = 0 Then

                Return System.DBNull.Value

            End If

        End If


        Return Page.Server.HtmlEncode(MyBase.Text)


    End Function


    Public Sub SetValue(ByVal BoundValue As Object) Implements IDataBoundControl.SetValue

        If IsDBNull(BoundValue) Then

            MyBase.Text = String.Empty

        Else

            MyBase.Text = Page.Server.HtmlDecode(BoundValue)

        End If

    End Sub


    Protected Overrides Sub OnDataBinding(ByVal e As System.EventArgs)

        Try

            MyBase.OnDataBinding(e)

        Catch ex As Exception

            RaiseBubbleEvent(Me, New ControlErrorEventArgs(ex))

        End Try

    End Sub


    Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)

        Try

            MyBase.OnPreRender(e)

        Catch ex As Exception

            RaiseBubbleEvent(Me, New ControlErrorEventArgs(ex))

        End Try

    End Sub


End Class

Compile and Deploy

Now, all we have to do is compile the project, take the resulting DLL, and place that in the site's /bin directory. Then, use the <Register> tag to register the control in the form and we're ready to use it.

<Register TagPrefix="cctxt" NameSpace="MyCompany.XMPControls.Form" Assembly="MyCompany.XMPControls" />

<AddForm>
  ...
  <cctxt:HtmlEncodeTextBox id="txtBio" TextMode="Multiline" Height="100" Width="400" DataField="Bio" />
  ...
</AddForm>

<EditForm>
 ...
  <cctxt:HtmlEncodeTextBox Id="txtBio" TextMode="Multiline" Height="100" Width="400" DataField="Bio"/>
  ...
</EditForm>