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.
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>