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.
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.
The application framework tries the following sequence while loading a data controller in memory.
- Ask the Controller class implementation to return the data controller content stream by name. The default implementation returns nothing.
- 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.
- 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.
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.
This screen shot shows localized version of the web application without dynamic customization of user interface.
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>