In this post I will provide a discussion on how to apply auditing configurations to your XAF applications, I will use the build-in XAF Audit Trail module and the eXpand Logic module for extending the XAF Application Model.
Goal
Our goal is to be able to declaratively configure auditing for different scenarios. For example apply an auditing configuration to a group of objects that fit in a criterion on a predefined group of views only when these views are shown in the main window.
Background -The logic module
The logic module provides the API we will use to extend the Application Model. In eXpand there are a few modules which use this API. For example three of them live in the ModeArtifactState module. In the next image we can see the structure of two of them the ConditionalAction and the ConditionalController.

Bellow there is the third child of the ModelArtifactState module ObjectView structure. I want to draw your attention to the common attributes we see in both images. All they come from the Logic Module which has the engine to evaluate them. Moreover the Logic module allows us to define permissions and class attributes using the same common attributes.

(AdditionalViewControls, MasterDetail and ModelAdaptor also attach them selves to the Logic Module)
Building a new Audit Trail Module
Using the Logic module is quite easy and the benefits too many. We will go through all the steps for creating an new AuditTrail module. Make a note that we discuss a workflow that can be applied to almost any scenario.
1) The startup interface
First off we need an interface that will be the AuditTrail Rule needed from the Logic Module. Let’s go to XAF Audit Trail documentation to read what we can use to design that interface. In order to attach to the Logic Module this interface needs to derive from ILogicRule interface. Bellow we see the IAuditTrailRule interface with one property the which we can use later to configure the AuditMode from the model or by using Role permissions.
public interface IAuditTrailRule:ILogicRule {
ObjectAuditingMode? AuditingMode { get; set; }
}
2) The Rule context interface
public interface IContextAuditTrailRule : IAuditTrailRule, IContextLogicRule {
The IContextAuditTrailRule provides information about contexts such as Execution, Action, View, Frame. In the top most images of this post you see that our model already has this information. However I mentioned before that the Logic Module supports permissions and class attributes. So the IContextAuditTrailRule will be implemented by the AuditTrailAttribute, and the permission classes which they need to know about context but not about model.
3) The model interfaces
[ModelInterfaceImplementor(typeof(IContextAuditTrailRule), "Attribute")]
public interface IModelAuditTrailRule : IContextAuditTrailRule, IModelConditionalLogicRule<IAuditTrailRule> {
}
The Logic module will extend the Application Model with the IModelAuditTrailRule and will give a structure similar to the one you saw in the top most images of this post. The IModelConditionalLogicRule comes from the Logic module so we only need to use here.
In addition to IModelAuditTrailRule interface we need to feed the Logic module with a list of IModelAuditTrailRules, so let’s design them.
[ModelNodesGenerator(typeof(LogicRulesNodesGenerator))]
public interface IModelAuditTrailLogicRules : IModelNode, IModelList<IModelAuditTrailRule> {
}
The LogicRulesGenerator is a class already implemented in the Logic Module and is needed only in the case we design a LogicRuleAttribute (see step 5)
Next, we need a container interface for the IModelAudiTrailLogicRules like the next one.
public interface IModelLogicAuditTrail : IModelLogicContexts {
IModelAuditTrailLogicRules Rules { get; }
}
The IModelLogicContexts interface comes from the Logic module and it will provide all the context information that the IModelAuditTrail needs.
Finally, we need the root interface to extend the application model, as below.
public interface IModelApplicationAudiTrail:IModelNode {
IModelLogicAuditTrail AudiTrail { get; }
}
public sealed class XpandAuditTrailModule :XpandModuleBase {
public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
base.ExtendModelInterfaces(extenders);
extenders.Add<IModelApplication, IModelApplicationAudiTrail>();
}
4) The Rule Object class
All rules from all sources (model, attributes, permissions) ultimately are converted to LogicRule object instances. The LogicRule abstract class lives in the Logic module. So, we need to design a concrete LogicRule class for the Audit Trail module like the one bellow.
public class AuditTrailRule:LogicRule,IAuditTrailRule {
public AuditTrailRule(IContextAuditTrailRule auditTrailRule)
: base(auditTrailRule) {
AuditingMode=auditTrailRule.AuditingMode;
}
public ObjectAuditingMode? AuditingMode { get; set; }
The AuditTrailRule class must also implement the common IAuditTrailRule interface as the rest of the classes. Note that we also have to initialize any Audit Trail specific properties in the constrictor.
5) The class attribute (Optional)
Below is the AuditTrailRuleAttribute which must derive from the LogicRuleAttribute. The LogicRuleAttribute comes from the Logic module so we do not need to implement anything.
public sealed class AuditTrailRuleAttribute:LogicRuleAttribute,IContextAuditTrailRule {
public AuditTrailRuleAttribute(string id) : base(id) {
}
public ObjectAuditingMode? AuditingMode { get; set; }
To map the AuditTrailRuleAttribute to the Application Model we need to derive from the LogicRuleNodeUpdater as below. The LogicRuleNodeUpdater is implemented in the Logic Module.
public class AuditTrailRulesNodeUpdater : LogicRulesNodeUpdater<IAuditTrailRule, IModelAuditTrailRule> {
protected override void SetAttribute(IModelAuditTrailRule rule, IAuditTrailRule attribute) {
rule.Attribute = attribute;
}
}
6) The permission classes (Optional)
The design of the permission classes is equally simple. For example the permission must derive from LogicRulePermission and implement the IContextRule interface. Here the same as step 4 there is an extra step we need to initialize the properties in the ctor as you see below.
public class AuditTrailRulePermission:LogicRulePermission,IContextAuditTrailRule {
public const string OperationName = "AuditTrail";
public AuditTrailRulePermission(AuditTrailOperationPermissionData contextLogicRule)
: base(OperationName, contextLogicRule) {
AuditingMode=contextLogicRule.AuditingMode;
}
public override IList<string> GetSupportedOperations() {
return new[] { OperationName };
}
public ObjectAuditingMode? AuditingMode { get; set; }
To finish the permission support, we need a concrete implementation of the LogicRuleOperationPermissionData which comes from the Logic module.
public class AuditTrailOperationPermissionData : LogicRuleOperationPermissionData,IContextAuditTrailRule {
public AuditTrailOperationPermissionData(Session session) : base(session) {
}
public override IList<IOperationPermission> GetPermissions() {
return new IOperationPermission[] { new AuditTrailRulePermission(this) };
}
public ObjectAuditingMode? AuditingMode { get; set; }
7) The Logic Installer
To finish with the structure of our custom Audit Trail module we need a concrete implementation of the LogicInstaller class like the next one.
public class AuditTrailLogicInstaller : LogicInstaller<IAuditTrailRule, IModelAuditTrailRule> {
public AuditTrailLogicInstaller(IXpandModuleBase xpandModuleBase)
: base(xpandModuleBase) {
}
public override List<ExecutionContext> ExecutionContexts {
get { return new List<ExecutionContext> {ExecutionContext.ViewChanging}; }
}
public override LogicRulesNodeUpdater<IAuditTrailRule, IModelAuditTrailRule> LogicRulesNodeUpdater {
get { return new AuditTrailRulesNodeUpdater(); }
}
protected override IModelLogicWrapper GetModelLogicCore(IModelApplication applicationModel) {
var auditTrail = ((IModelApplicationAudiTrail)applicationModel).AudiTrail;
return new ModelLogicWrapper(auditTrail.Rules, auditTrail);
}
}
ExecutionContexts:
There I used an ExecutionContext.ViewChanging as my default context. You free to use any context its appropriate for your task. The logic engine will evaluate the IAuditTrailLogicRule only when ViewChaning.
LogicRulesNodeUpdater:
If you implement the optional AuditTrailLogicRuleAttribute you need to pass its model updater at this point.
GetModelLogicCore:
There we need to return a ModelLogicWrapper class with the IModelAuditTrailRules and the rest of the context. It is possible to share rules and contexts from any model path using a different contructor overload. For example we may design the IModelLogicAuditTrail interface like below.
public interface IModelLogicAuditTrail : IModelNode {
IModelAuditTrailLogicRules Rules { get; }
IModelExecutionContextsGroup ExecutionContextsGroup { get; }
}
So now the IModelLogicAuditTrail knows only about ExecutionContexts (no FrameTemplateContext, no Actions, no Views). For this configuration the appropriate ModelLogicWrapper instantiation is shown below.
protected override IModelLogicWrapper GetModelLogicCore(IModelApplication applicationModel) {
var auditTrail = ((IModelApplicationAudiTrail)applicationModel).AudiTrail;
return new ModelLogicWrapper(auditTrail.Rules, auditTrail.ExecutionContextsGroup);
}
Finally we need to register the installer in the contructor of our new module as shown below.
public sealed class XpandAuditTrailModule :XpandModuleBase {
public XpandAuditTrailModule() {
LogicInstallerManager.RegisterInstaller(new AuditTrailLogicInstaller(this));
}
The Audit Trail structure is ready! Bellow we see the model for an AuditTrailRule similar to the one of the top most images of this post.

The Logic module knows what to do with all the attributes except the AuditingMode. Next we discuss how to write logic for this attribute.
The Audit Trail Logic
To write logic for the AuditTrailRule, we have to subscribe to LogicRuleViewController RuleExecture event. The LogicRuleViewController lives of course in the Logic module and we do not need to implement it.
public class AuditTrailRuleViewController:ViewController {
LogicRuleViewController _logicRuleViewController;
ObjectAuditingMode _oldObjectAuditingMode;
protected override void OnFrameAssigned() {
base.OnFrameAssigned();
Frame.Disposing+=FrameOnDisposing;
_logicRuleViewController = Frame.GetController<LogicRuleViewController>();
_logicRuleViewController.LogicRuleExecutor.LogicRuleExecute+=LogicRuleExecutorOnLogicRuleExecute;
}
void FrameOnDisposing(object sender, EventArgs eventArgs) {
Frame.Disposing-=FrameOnDisposing;
_logicRuleViewController.LogicRuleExecutor.LogicRuleExecute -= LogicRuleExecutorOnLogicRuleExecute;
}
void LogicRuleExecutorOnLogicRuleExecute(object sender, LogicRuleExecuteEventArgs logicRuleExecuteEventArgs) {
var logicRuleInfo = logicRuleExecuteEventArgs.LogicRuleInfo;
var auditTrailRule = logicRuleInfo.Rule as IAuditTrailRule;
if (auditTrailRule != null) {
if (!logicRuleInfo.InvertCustomization) {
ApplyCustomization(auditTrailRule);
} else {
InvertCustomization(auditTrailRule);
}
}
}
void InvertCustomization(IAuditTrailRule auditTrailRule) {
var auditTrailService = AuditTrailService.Instance;
if (auditTrailRule.AuditingMode.HasValue)
auditTrailService.ObjectAuditingMode = _oldObjectAuditingMode;
}
void ApplyCustomization(IAuditTrailRule auditTrailRule) {
var auditTrailService = AuditTrailService.Instance;
if (auditTrailRule.AuditingMode.HasValue) {
_oldObjectAuditingMode = auditTrailService.ObjectAuditingMode;
auditTrailService.ObjectAuditingMode = auditTrailRule.AuditingMode.Value;
}
}
LogicRuleExecutorOnLogicRuleExecute method:
This method will be called for all ILogicRule interfaces, for example the new IAuditTrailRule and if you use the MasterDetail module the IMasterDetailRule. So first thing is to cast the returned Rule to IAuditTrailRule and if not it’s the right place to write our logic. In step 7 in the AuditTrailInstaller we used only ExecutionContext.ViewChaning so this method will be called only when ViewChanging and rule is IAuditTrailRule. In addition the LogicRuleInfo provides information about inverting any customization.
Audit Trail Rules in Action
In next eXpandFramework version 13.1.6.3 the Audit Trail is fully functional. More attributes are included and also an IModelMemberAuditTrail to allow creation of the collection needed to monitor auditing from the UI.
In the next image we see a rule that will force the Auditing service to use LightWeght mode for all objects.

Next is a rule applied directly to the Customer class that will audit the members included in the Customer_LastName_Age_Group in all Customer DetailViews
[AuditTrailRule("Audit_Customer", AuditTrailMembersContext = "Customer_LastName_Age_Group",ViewType = ViewType.DetailView)]
public class Customer : Person {
here is the rule that the Logic module will generate for the above AuditTrailAttribute.

and finally using permissions a rule that will audit Employee members under the “Emplyee_Members” context.
var permissionData = ObjectSpace.CreateObject<AuditTrailOperationPermissionData>();
permissionData.ObjectTypeData = typeof (Employee);
permissionData.ID = "audit_emplyee";
permissionData.AuditTrailMembersContext = "Employee_Members";
((XpandRole) userRole).Permissions.Add(permissionData);
You can explore how this module works in Demos/Modules/AuditTrail/AuditTrailTester.sln
Until next time,
Happy XAF’ing!
XAF has a build-in mechanism for customizing the layout’s. However nothing can be compared with the power of native CSS styles. In this post I will discuss eXpand’s implementation of CSS styles in the XAF application model.
How XAF renders a layout
To render a web layout XAF uses the WebLayoutManager class. This class queries all the IModelViewLayoutElements of a DetailView.Model.Layout and renders WebControls accordingly. For example for an IModelLayoutViewItem which is an interface that links the layout element with a property editor, the WebLayoutManager will render two controls. The first one will hold the caption/label and the second one will be the actual control depending on the type of the property editor. Take a look how an looks IModelLayoutViewItem in the Model Editor.

As you can see XAF supports a lot of attributes by default! Due to the large number of attributes WebLayoutManager introduces a table to hold and position our controls and a div above it to allow more flexibility in configuration.
It is also possible to use more more interfaces to design a layout. In the next image we see what is supported by default. However WebLayoutManager behavior is the same.

What we need to style?
Of course everything fast-flexible and without introducing complexities and XAF application model is the perfect tool for this. To summurize the previous paragraph we may wish to style
- The actual WebControl
- The label of the WebControl
- The container table
- The container div
- The whole view
How we get control instances
All XAF controls are rendered by property editors. For the web there is an abstract WebPropertyEditor (see a simplistic inheritance view).
It is enough to create a Controller then query all WebPropertyEditor ViewItems and subscribe to ControlCreated event. When the event raised we can do webPropertyEditor.Control and get the instance we need. Let’s see this a pseudo code snippet.
public class LayoutStyleController:ViewController<DetailView>,IModelExtender {
protected override void OnActivated() {
base.OnActivated();
foreach (var item in View.GetItems<WebPropertyEditor>()) {
item.ControlCreated+=ItemOnControlCreated;
}
}
void ItemOnControlCreated(object sender, EventArgs eventArgs) {
var webPropertyEditor = ((WebPropertyEditor) sender);
webPropertyEditor.ControlCreated-=ItemOnControlCreated;
//here we have our webcontrol instance
var control = webPropertyEditor.Control;
}
How to get the layout container
WebLayoutManager is designed with the highest standards, so it is as easy as subscribing to its LayoutCreated event and getting the value of the Container property
protected override void OnActivated() {
base.OnActivated();
View.LayoutManager.LayoutCreated += LayoutManager_LayoutCreated;
}
void LayoutManager_LayoutCreated(object sender, EventArgs e) {
View.LayoutManager.LayoutCreated-=LayoutManager_LayoutCreated;
//here we have our webcontrol instance
WebControl container = (WebControl) View.LayoutManager.Container;
}
How to get the rest of the objects we need to style
All the rest are only known from the WebLayoutManager which renders them using an advanced template mechanism. It is of course possible to override it however I want to continue working with pluggable controllers. So I will create an interface to help me parse those templates from a Controller.
public interface IWebLayoutManager {
event EventHandler<TemplateInstantiatedEventArgs> Instantiated;
}
public class XpandLayoutManager : WebLayoutManager, IWebLayoutManager {
ViewItemsCollection _detailViewItems;
public event EventHandler<TemplateInstantiatedEventArgs> Instantiated;
protected virtual void OnInstantiated(TemplateInstantiatedEventArgs e) {
var handler = Instantiated;
if (handler != null) handler(this, e);
}
protected override LayoutBaseTemplate CreateLayoutItemTemplate() {
var layoutBaseTemplate = base.CreateLayoutItemTemplate();
layoutBaseTemplate.Instantiated += LayoutBaseTemplateOnInstantiated;
return layoutBaseTemplate;
}
protected override LayoutBaseTemplate CreateLayoutGroupTemplate() {
var layoutBaseTemplate = base.CreateLayoutGroupTemplate();
layoutBaseTemplate.Instantiated += LayoutBaseTemplateOnInstantiated;
return layoutBaseTemplate;
}
void LayoutBaseTemplateOnInstantiated(object sender, TemplateInstantiatedEventArgs templateInstantiatedEventArgs) {
OnInstantiated(templateInstantiatedEventArgs);
}
Also I need to plug this custom XpandLayoutManager to my WebApplication descendant as below.
public class XpandWebApplication : WebApplication {
protected override LayoutManager CreateLayoutManagerCore(bool simple) {
return new XpandLayoutManager();
}
Now it is possible to subscribe to to XpandLayoutManager Instanciated event from a controller and parse the templates to discover the label, the container table and the container div.
Extending the model
Having all the WebControls instances we want to style it is time to extend the model with a few interfaces so to control the styling from there. Bellow is the interface we need
public interface IModelLayoutStyle : IModelNode {
FontStyle FontStyle { get; set; }
Color FontColor { get; set; }
Color BackColor { get; set; }
string CssClass { get; set; }
string Style { get; set; }
}
This interface can be used to extend IModelDetailView and IModelLayoutGroup to control the style of the whole view and of a grouped layout element as illustrated below.
public class LayoutStyleController:ViewController<DetailView>,IModelExtender {
public void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
extenders.Add<IModelDetailView, IModelLayoutStyle>();
extenders.Add<IModelLayoutGroup, IModelLayoutStyle>();
}
Now for the IModelLayoutViewItem as we discussed in the beginning there are three controls we need to style (actual, label and container). So we need to introduce another interface and to extend the IModelLayoutViewItem like the next one.
public interface IModelLayoutViewItemStyle {
ILayoutStyle LayoutStyle { get; }
}
public interface ILayoutStyle:IModelNode {
IModelLayoutStyle Container { get; }
IModelLayoutStyle Control { get; }
IModelLayoutStyle Caption { get; }
}
After extending the IModelLayoutViewItem the Model Editor now will display the next structure.

Finally it’s time to write the StyleProvider class and put them all in a reusable controller. However I will skip adding so much code in a post and I will redirect you to eXpandFramework GitHub repository (just grab the LayoutStyleController.cs and extend the WebLayoutManager as discussed above).
Demo
Bellow you can see a cool view a designed with the CSS in Application Model and as always with zero code lines.

Those of you that come often to eXpand forums will recognize for sure that this is a replicate of the Kunena forums (see for your self
)!

This functionality is available with eXpandFramework 13.1.5.19.
Until next time,
Happy XAF’ing to all!