IPortable: Easily export DotNetNuke® content from your module to deploy on your production server (in VB.NET and C#)

Note: To save a date you should use ToBinary

The IPortable interface will allow the users of your module to develop content on a development server and easily deploy this content to a production server. You will want to implement this interface in practically every module you create no matter how simple it is. The good news is that this task is very easy to do and does not require advanced programming or a significant amount of time.

What IPortable Will Do For You

Let's say you have a development server at your company and you created a survey using the Survey module (using the 04.00.00 or higher version. A link to the Beta is available at the end of this article).

You now want to put the Survey on your production server. To do so you would simply follow these steps:

1) From the menu of the Survey module select Export Content

2) Next, from the Export Module page click the Export button

3) While logged in as the portal Administrator select File Manger from the Admin menu


(do this while logged in as portal Administrator not as the portal Host using the Host account because you will not see the correct directory in the File Manager)

We see the .xml file that has been created (in this example content.Survey.Survey.xml)

4) Import the .xml file to your production server using these steps:

5) Next place an instance of the Survey module on a page (click here for directions on adding a module to a page) and from the Survey module's menu select Import Content

6) The .xml file is show in the drop-down. Click the Import button

And you're done!

Implementing IPortable

To implement this functionality in your own custom DotNetNuke module you simply:

Specify a business controller class

The first thing you need to do is indicate a business controller class in the module definition for your module (If you have already started working on your module and you neglected to do this step initially you will have to delete the module definition and recreate it).

Here are the steps I used to create the module definition for the Survey module:

While logged into the DotNetNuke site as the "host" account, from the menu bar I selected Host and then selected Module Definitions

I clicked on the black arrow that is pointing down to make the fly-out menu to appear. On that menu I selected Create New Module

In the Edit Module Definitions page I entered:

I then clicked UPDATE and completed the module definition (see my tutorial that describes how to create a module definition)

Add the Line Implements Entities.Modules.IPortable to the Business Controller Class

I created a SurveyController.vb class file

In that class I added the line Implements Entities.Modules.IPortable

Visual Studio then automatically adds the two stub methods.

Public Function ExportModule(ByVal ModuleID As Integer) As String Implements Entities.Modules.IPortable.ExportModule

End Function

Public
Sub ImportModule(ByVal ModuleID As Integer, ByVal Content As String, ByVal Version As String, ByVal UserID As Integer) Implements Entities.Modules.IPortable.ImportModule

End
Sub

Create a ExportModule method


When the users of your module selects Export Content, the DotNetNuke framework will call the ExportModule method that you create and pass it a ModuleID parameter. It is expecting your method to return an .xml formatted response as a String data type.

When implementing this for the Survey module I can see that the Surveys are contained in the Surveys table, but the options (for example "First Choice") are contained in the SurveyOptions table.

For each Survey in the Surveys table, I will need to export the matching options for that survey in the SurveyOptions table. Another thing to notice is that while the primary key for the Surveys table is SurveyID, the field that is important right now is the ModuleID field. In the DotNetNuke architecture the ModuleID is an important parameter. A ModuleID represents a single instance of a module and is used to segment the data for each instance of that module in the database. A ModuleID is unique across all portals in a DotNetNuke installation.

Here is my implementation of the ExportModule method:

Public Function ExportModule(ByVal ModuleID As Integer) As String Implements DotNetNuke.Entities.Modules.IPortable.ExportModule

Dim
strXML As New StringBuilder()
Dim settings As New XmlWriterSettings()
settings.Indent =
True
settings.OmitXmlDeclaration = True
Dim
Writer As XmlWriter = XmlWriter.Create(strXML, settings)

'Outer Loop - To build the Surveys
Dim colSurveys As List(Of SurveyInfo) = GetSurveys(ModuleID)
If colSurveys.Count > 0 Then
Writer.WriteStartElement("surveys")
Dim SurveyInfo As SurveyInfo
For Each SurveyInfo In colSurveys
Writer.WriteStartElement(
"survey")
Writer.WriteElementString(
"question", SurveyInfo.Question)
Writer.WriteElementString(
"vieworder", SurveyInfo.ViewOrder)
Writer.WriteElementString(
"createdbyuser", SurveyInfo.CreatedByUser)
Writer.WriteElementString(
"createddate", SurveyInfo.CreatedDate)
Writer.WriteElementString(
"optiontype", SurveyInfo.OptionType.ToString)

    'Inner Loop - To build the Options for each Survey
    Dim colSurveyOptions As List(Of SurveyOptionInfo) = SurveyOptionController.GetSurveyOptions(SurveyInfo.SurveyId)
    If colSurveyOptions.Count > 0 Then
    Writer.WriteStartElement("surveyoptions")
    Dim SurveyOptionInfo As SurveyOptionInfo
    For Each SurveyOptionInfo In colSurveyOptions
    Writer.WriteStartElement(
"surveyoption")
    Writer.WriteElementString(
"optionname", SurveyOptionInfo.OptionName)
    Writer.WriteElementString(
"iscorrect", SurveyOptionInfo.IsCorrect)
    Writer.WriteElementString(
"vieworder", SurveyOptionInfo.ViewOrder)
    Writer.WriteEndElement()
    Next ' Retrieve the next SurveyOption
    Writer.WriteEndElement()
    End If
    Writer.WriteEndElement()

Next
Writer.WriteEndElement()
Writer.Close()

Else
' There is nothing to export
Return String.Empty
End If
Return
strXML.ToString()

End Function

In C#:

string DotNetNuke.Entities.Modules.IPortable.ExportModule(int ModuleID)
{
StringBuilder strXML = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent =
true;
settings.OmitXmlDeclaration =
true;
XmlWriter Writer = XmlWriter.Create(strXML, settings);

// Outer Loop - To build the Surveys
List<SurveyInfo> colSurveys = GetSurveys(ModuleID);
if ((colSurveys.Count > 0))
{
Writer.WriteStartElement(
"surveys");
foreach (SurveyInfo colSurveyInfo in colSurveys)
{
Writer.WriteStartElement(
"survey");
Writer.WriteElementString(
"question", colSurveyInfo.Question);
Writer.WriteElementString(
"vieworder", colSurveyInfo.ViewOrder.ToString());
Writer.WriteElementString(
"createdbyuser", colSurveyInfo.CreatedByUser.ToString());
Writer.WriteElementString(
"createddate", colSurveyInfo.CreatedDate.ToShortDateString());
Writer.WriteElementString(
"optiontype", colSurveyInfo.OptionType.ToString());

  // Inner Loop - To build the Options for each Survey
  List<SurveyOptionInfo> colSurveyOptions = SurveyOptionController.GetSurveyOptions(colSurveyInfo.SurveyId);
  if ((colSurveyOptions.Count > 0))
  {
  Writer.WriteStartElement(
"surveyoptions");
  foreach (SurveyOptionInfo colSurveyOptionInfo in colSurveyOptions)
  {
    Writer.WriteStartElement(
"surveyoption");
    Writer.WriteElementString(
"optionname", colSurveyOptionInfo.OptionName);
    Writer.WriteElementString(
"iscorrect", colSurveyOptionInfo.IsCorrect.ToString());
    Writer.WriteElementString(
"vieworder", colSurveyOptionInfo.ViewOrder.ToString());
    Writer.WriteEndElement();
  }

// Retrieve the next SurveyOption
Writer.WriteEndElement();
  }
    Writer.WriteEndElement();
  }
Writer.WriteEndElement();
Writer.Close();
}
else
{

// There is nothing to export
return String.Empty;
}
return strXML.ToString();
}

Here is a sample of the output created by the DotNetNuke framework from the output of this method:

<?xml version="1.0" encoding="utf-8" ?>
<content type="Survey" version="04.00.00">
    <surveys>
    <survey>
        <question><![CDATA[Question Number One]]></question>
        <vieworder><![CDATA[-1]]></vieworder>
        <createdbyuser><![CDATA[-1]]></createdbyuser>
        <createddate><![CDATA[9/3/2006 4:46:45 PM]]></createddate>
        <optiontype><![CDATA[R]]></optiontype>
        <surveyoptions>
            <surveyoption>
                <optionname><![CDATA[First Choice]]></optionname>
                <iscorrect><![CDATA[False]]></iscorrect>
                <vieworder><![CDATA[0]]></vieworder>
            </surveyoption>
            <surveyoption>
                <optionname><![CDATA[Second Choice]]></optionname>
                <iscorrect><![CDATA[False]]></iscorrect>
                <vieworder><![CDATA[1]]></vieworder>
            </surveyoption>
        </surveyoptions>
    </survey>
    </surveys>
</content>

Create a ImportModule method


When the users of your module select Import Content, the DotNetNuke framework will call the ImportModule method that you create and pass it a ModuleID parameter and a Content parameter as well as Version and UserID. It expects your controller class to process the contents of the Content parameter which has a string data type. The Content parameter will contain the data in the form of the sample above. Your ImportModule method will not return anything. Your method will usually just insert the data passed to in in the Content parameter into the database.

Here is my implementation of the ImportModule method:

Public Sub ImportModule(ByVal ModuleID As Integer, ByVal Content As String, ByVal Version As String, ByVal UserId As Integer) Implements DotNetNuke.Entities.Modules.IPortable.ImportModule

'Import the Surveys
'Outer Loop - To insert the Surveys
Dim intCurrentSurvey As Integer
Dim
xmlSurvey As XmlNode
Dim xmlSurveys As XmlNode = GetContent(Content, "surveys")
For Each xmlSurvey In xmlSurveys
Dim SurveyInfo As New SurveyInfo
SurveyInfo.ModuleId = ModuleID
SurveyInfo.Question = xmlSurvey.Item(
"question").InnerText
SurveyInfo.ViewOrder = xmlSurvey.Item(
"vieworder").InnerText
SurveyInfo.CreatedByUser = xmlSurvey.Item(
"createdbyuser").InnerText
SurveyInfo.CreatedDate = xmlSurvey.Item(
"createddate").InnerText
SurveyInfo.OptionType = xmlSurvey.Item(
"optiontype").InnerText
'Add the Survey to the database
intCurrentSurvey = AddSurvey(SurveyInfo)

'Inner Loop - To insert the Survey Options
Dim xmlSurveyOption As XmlNode
Dim xmlSurveyOptions As XmlNode = xmlSurvey.SelectSingleNode("surveyoptions")
For Each xmlSurveyOption In xmlSurveyOptions
Dim SurveyOptionInfo As New SurveyOptionInfo
SurveyOptionInfo.SurveyId = intCurrentSurvey
SurveyOptionInfo.OptionName = xmlSurveyOption.Item(
"optionname").InnerText
SurveyOptionInfo.IsCorrect = xmlSurveyOption.Item(
"iscorrect").InnerText
SurveyOptionInfo.ViewOrder = xmlSurveyOption.Item(
"vieworder").InnerText
'Add the Survey to the database
SurveyOptionController.AddSurveyOption(SurveyOptionInfo)
Next

Next
' Retrieve the next Survey

End Sub

in C#:

void DotNetNuke.Entities.Modules.IPortable.ImportModule(int ModuleID, string Content, string Version, int UserID)
{
//Import the Surveys
//Outer Loop - To insert the Surveys
int intCurrentSurvey;
XmlNode xmlSurveys = DotNetNuke.Common.Globals.GetContent(Content, "surveys");
foreach (XmlNode xmlSurvey in xmlSurveys)
{
SurveyInfo SurveyInfo = new SurveyInfo();
SurveyInfo.ModuleId = ModuleID;
SurveyInfo.Question = xmlSurvey[
"question"].InnerText;
SurveyInfo.ViewOrder =
Convert.ToInt32(xmlSurvey["vieworder"].InnerText);
SurveyInfo.CreatedByUser =
Convert.ToInt32(xmlSurvey["createdbyuser"].InnerText);
SurveyInfo.CreatedDate =
Convert.ToDateTime(xmlSurvey["createddate"].InnerText);
SurveyInfo.OptionType = xmlSurvey[
"optiontype"].InnerText;

// Add the Survey to the database
intCurrentSurvey = AddSurvey(SurveyInfo);
//Inner Loop - To insert the Survey Options
XmlNode xmlSurveyOptions = xmlSurvey.SelectSingleNode("surveyoptions");
foreach (XmlNode xmlSurveyOption in xmlSurveyOptions)
{
SurveyOptionInfo SurveyOptionInfo = new SurveyOptionInfo();
SurveyOptionInfo.SurveyId = intCurrentSurvey;
SurveyOptionInfo.OptionName = xmlSurveyOption[
"optionname"].InnerText;
SurveyOptionInfo.IsCorrect =
Convert.ToBoolean(xmlSurveyOption["iscorrect"].InnerText);
SurveyOptionInfo.ViewOrder =
Convert.ToInt32(xmlSurveyOption["vieworder"].InnerText);

// Add the Survey to the database
SurveyOptionController.AddSurveyOption(SurveyOptionInfo);
}
}
}

Now, return to the module definition you created and click the Update link.

The Portable feature will now have check box.

That's it. You're done.

It is not a lot of code, yet the functionality that this creates for the end user is significant.

Hopefully you will find this example helpful because if you examine the code you will see that I had to insert a survey, retrieve the SurveyID and then use that SurveyID when inserting the matching survey options.

This interface represents another example of why you would choose to use the DotNetNuke framework for a project rather than coding the project completely from scratch. By simply creating two simple methods you can provide your project with a process to move content between servers. Unlike the sometimes awkward and error prone methods of direct data transfer, you will have .xml files that can be easily backed up and versioned.  Most importantly the data is separated from the content (for example the survey results are not exported just the surveys). In addition you have complete control over what defines content.

In future postings I will cover other Interfaces that are important to module developers such as IActionable and ISearchable,

The Survey code covered above is BETA and has not been released. You can download the code using this link. Before you upload it to an existing DNN4 site you have to remove these two assemblies from the "\bin" directory of your existing DNN site.
    DotNetNuke.Modules.Survey.dll
    DotNetNuke.Modules.Survey.SqlDataProvider.dll

A special thanks to Stefan Cullmann for the enhancements to the xml generation code.

[Back to: The ADefWebserver DotNetNuke HELP WebSite]

DotNetNuke® is a registered trademark of Perpetual Motion Interactive Systems Inc.