Data Controllers / Framework
Virtualization

Code On Time applications rely on data controller descriptors to determine the expected application behavior at runtime.  A typical data controller XML file packages information about fields, data views, action state machine, and optional linked business rules.

The application framework interprets this data. The server components of the framework parse the XML files and prepare internal structures to process requests to select, update, insert, and delete data. The AJAX client portion of the application framework renders data views and available state machine actions in web browsers.

From the application framework perspective, the physical location of data controller files is unknown. Business rules classes are not aware and don’t care about the location of data controllers.

Web Site Factory applications have all data controller files stored in ~/Controllers folder.

Data controllers in a Web Site Factory application

All other project types are implemented as a Visual Studio solution with several projects. The core application framework and data controller files are located in the Class Library project of the solution. The data controllers are marked as resources. The entire text of XML files is compiled into the DLL of the class library.

The next screen shot shows a class library in a SharePoint Factory project.

Data controllers in SharePoint Factory application

The application framework tries the following sequence while loading a data controller in memory.

  1. Ask the Controller class implementation to return the data controller content stream by name. The default implementation returns nothing.
  2. Try loading the data controller from the binary resources of the application. All project types other than Web Site Factory will return the resource stream of a data controller.
  3. Try loading the data controller file from ~/Controllers folder. This applies to Web Site Factory only.

Virtualization of a data controller is a replacement of an entire data controller or some parts of it at runtime.

If you want to store an application data controller outside of application code base then add the following partial class definition. Your class will complement the default application framework implementation of controller functionality.

C#:

using System;
using System.IO;

namespace MyCompany.Data
{

    public partial class Controller
    {
        public override Stream GetDataControllerStream(string controller)
        {
            string fileName = String.Format(@"c:\clients\Acme\{0}.xml", controller);
            if (File.Exists(fileName))
                return new MemoryStream(File.ReadAllBytes(fileName));
            return DefaultDataControllerStream;
        }
    }
}

Visual Basic:

Imports Microsoft.VisualBasic
Imports System.IO

Namespace MyCompany.Data

    Partial Public Class Controller
        Public Overrides Function GetDataControllerStream(controller As String) As Stream
            Dim fileName = String.Format("c:\\clients\Acme\{0}.xml", controller)
            If (File.Exists(fileName)) Then
                Return New MemoryStream(File.ReadAllBytes(fileName))
            End If
            Return DefaultDataControllerStream
        End Function
    End Class

End Namespace

The sample above loads a customized version of the data controller Customers if the file is found in C:\Clients\Acme folder.  Otherwise the application will proceed to load the default data controller defined in the application.

If you have a multi-tenant web application and desire to offer customized data controllers for a specific group of users then consider using this method of customization. You can inspect the incoming web request or session variables and identify the user group. Load the correct data controller based on that info.

We recommend using this method of data controller virtualization when you need to branch the data controller definition.

You can also customize the contents of a data controller after the file has been loaded by the application framework in the memory and is ready to be used in the web request processing. This method of virtualization involves changing a portion of a data controller definition while leaving the rest of it intact.

Implement either a dedicated business rules class for your controller or create a shared business rules class to have all application data controllers share a common functionality.

Let’s create a partial data controller virtualization sample with shared business rules.

Select your project on the start page of the web application generator and choose Settings, click Business Logic Layer, and select Shared Business Rules. Check the box that enables shared business rules. Click Finish and generate your project.

The class implementing shared business rules will either be in ~/App_Code/Rules folder of your project or in the ~/Rules folder of the solution class library.

Shared Business Rules class implementation in a Web Site Factory project

Make sure that any existing custom business rules inherit from the class [YourNamespace].Rules.SharedBusinessRules. Replace [YourNamespace] with the namespace of your project.

The following implementation of shared business rules alters data controller Customers based on the current user interface culture.

If the culture is “en-US” then the Region field label is changed to State; the Postal Code field label is changed to Zip Code; and all instances of Country data field are “hidden” from the application users.

Notice that the changes do not effect the actual file that contains the definition of Customers data controller.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;
using System.Xml.XPath;
using System.Xml;

namespace MyCompany.Rules
{
    public partial class SharedBusinessRules : MyCompany.Data.BusinessRules
    {
        public override bool SupportsVirtualization(string controllerName)
        {
            if (controllerName == "Customers")
                return true;
            else
                return false;
        }

        public override void VirtualizeController(string controllerName,
            XPathNavigator navigator, XmlNamespaceManager resolver)
        {
            if (controllerName == "Customers")
            {
                if (System.Threading.Thread.CurrentThread.CurrentUICulture.Name == "en-US")
                {
                    // Change "Region" label to "State" for users from the USA
                    XPathNavigator regionLabel = navigator.SelectSingleNode(
                        "/c:dataController/c:fields/c:field[@name='Region']/@label",
                        resolver);
                    if (regionLabel != null)
                        regionLabel.SetValue("State");
                    // Change "Postal Code" label to "Zip Code" for users from the USA
                    XPathNavigator postalCodeLabel = navigator.SelectSingleNode(
                        "/c:dataController/c:fields/c:field[@name='PostalCode']/@label",
                        resolver);
                    if (postalCodeLabel != null)
                        postalCodeLabel.SetValue("Zip Code");
                    // Mark all data fields named "Country" as "hidden"
                    XPathNodeIterator countryFieldIterator = navigator.Select(
                        "//c:dataField[@fieldName='Country']",
                        resolver);
                    while (countryFieldIterator.MoveNext())
                    {
                        XPathNavigator dataFieldNav = countryFieldIterator.Current;
                        if (dataFieldNav.MoveToAttribute("hidden", String.Empty))
                            dataFieldNav.SetValue("true");
                        else
                            dataFieldNav.CreateAttribute(
                                String.Empty, "hidden", String.Empty, "true");
                    }
                }
            }
        }
    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq
Imports System.Xml.XPath
Imports System.Xml

Namespace MyCompany.Rules

    Partial Public Class SharedBusinessRules
        Inherits MyCompany.Data.BusinessRules

        Public Overrides Function SupportsVirtualization(controllerName As String) As Boolean
            If controllerName = "Customers" Then
                Return True
            Else
                Return False
            End If

        End Function

        Public Overrides Sub VirtualizeController(controllerName As String,
                                                  navigator As XPathNavigator,
                                                  resolver As XmlNamespaceManager)
            If (controllerName = "Customers") Then
                If (System.Threading.Thread.CurrentThread.CurrentUICulture.Name = "en-US") Then
                    ' Change "Region" label to "State" for users from the USA
                    Dim regionLabel As XPathNavigator = navigator.SelectSingleNode(
                        "/c:dataController/c:fields/c:field[@name='Region']/@label",
                        resolver)
                    If (Not regionLabel Is Nothing) Then
                        regionLabel.SetValue("State")
                    End If
                    ' Change "Postal Code" label to "Zip Code" for users from the USA
                    Dim postalCodeLabel As XPathNavigator = navigator.SelectSingleNode(
                        "/c:dataController/c:fields/c:field[@name='PostalCode']/@label",
                        resolver)
                    If (Not postalCodeLabel Is Nothing) Then
                        postalCodeLabel.SetValue("Zip Code")
                    End If
                    ' Mark all data fields named "Country" as "hidden"
                    Dim countryFieldIterator As XPathNodeIterator = navigator.Select(
                        "//c:dataField[@fieldName='Country']",
                        resolver)
                    While (countryFieldIterator.MoveNext())
                        Dim dataFieldNav = countryFieldIterator.Current
                        If (dataFieldNav.MoveToAttribute("hidden", String.Empty)) Then
                            dataFieldNav.SetValue("true")
                        Else
                            dataFieldNav.CreateAttribute(
                                String.Empty, "hidden", String.Empty, "true")
                        End If
                    End While
                End If
            End If
        End Sub

    End Class
End Namespace

This is the screen shot that shows the effect of the shared business rules. User interface is altered at runtime when English (United States) localization is selected.

'Customers' data controller affected by virtualization

This screen shot shows localized version of the web application without dynamic customization of user interface.

'Customers' data controller not affected by virtualization

The portion of the data controller definition that is being changed at runtime is shown next.

<fields>
  . . . . .
  <field name="Region" type="String" label="Region" />
  <field name="PostalCode" type="String" label="Postal Code" />
  <field name="Country" type="String" label="Country" />
  . . . . . 
</fields>
<views>
  <view id="grid1" type="Grid" commandId="command1" label="Customers">
    <headerText>$DefaultGridViewDescription</headerText>
    <dataFields>
      . . . . .
      <dataField fieldName="Country" columns="15" />
      . . . . .
    </dataFields>
  </view>
  <view id="editForm1" type="Form" commandId="command1" label="Review Customers">
    <headerText>$DefaultEditViewDescription</headerText>
    <categories>
      <category id="c1" headerText="Customers" newColumn="true">
        <description><![CDATA[$DefaultEditDescription]]></description>
        <dataFields>
          . . . . .
          <dataField fieldName="Country" columns="15" />
          . . . . .
        </dataFields>
      </category>
    </categories>
  </view>
  <view id="createForm1" type="Form" commandId="command1" label="New Customers">
    <headerText>$DefaultCreateViewDescription</headerText>
    <categories>
      <category id="c1" headerText="New Customers" newColumn="true">
        <description><![CDATA[$DefaultNewDescription]]></description>
        <dataFields>
          . . . . .
          <dataField fieldName="Country" columns="15" />
          . . . . .
        </dataFields>
      </category>
    </categories>
  </view>
</views>