Friday, August 30, 2013

Model View inheritance in next eXpandFramework version

see also http://apobekiaris.blogspot.gr/2013/11/partial-view-inheritance.html

Right now in XAF it is possible to Clone an Application Model node by simply right click on it and choose Clone from the context menu. What will happen is that an identical node will be created. However by design, further modifications are not synchronized between mother-child nodes. I am going to demonstrate a different approach that does synchronize child nodes and you can find it in eXpandFramework 13.1.6.9 or later when using any module.
In the next image we see two Application Model views, first the MDO_DetailView -the base node- and then the RDO_DetailView which its ModelClass.Type descents from the MDO_DetailView ModelClass.Type.
image
In any Object View it is now possible to add MergeDifference items like the “Everything MDO” we see in the above picture. The Lookup datasource of the View attribute enumerates all views of the hierarchy chain of the current View.ModelClass.Type. For now, there are two Strategies (Everything and Only Layout). The merging algorithm will create a model node out of the differences of all loaded modules. For example the RDO_DetailView will have the modified/bold Layout of the MDO_DetailView layout as startup layout.
Forgot to mention that in this version you need to re open the Model Editor every time you make a change in a MergedDifferences collection.
For any questions, feel free to use the eXpandFramework forums.
Happy XAF’ing to all!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Tuesday, August 20, 2013

Declarative data auditing

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.

image

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.

image

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

 image

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.

image

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.

image

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!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Thursday, August 15, 2013

Conditional assemblies merged in ModelArtifact in 13.1.6

Hello all,

The following modules do not exist anymore in 13.1.6.1

ConditionalActionState, ConditionalControllerState, ConditionalObjectView now are under the ModelArtifact omprella.

Also Logic.Win merged with Logic module. So you need to remove all code and change the usings in case you have them install in your projects.

There are a few breaking changes in 13.1.6.1, so please post in the forums in case you get lost with the new super fast and flexible logic ruling engine.

In short everything that has to do with Logic Rules is now handled by the logic modules and there are a few classes like LogicRuleManager, LogicInstallerManager, LogicRuleCollector, LogicRuleEvaluator etc that do all the hard job. The design now is very clean and easy to follow and I am sure it will reduce many strange issues we had and will help to build new modules based on the Logic engine!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Wednesday, August 7, 2013

Flexible-fast layouts with CSS styles in the XAF application model

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.

image

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.

image

 

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

  1. The actual WebControl
  2. The label of the WebControl
  3. The container table
  4. The container div
  5. 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).
image
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 : WebLayoutManagerIWebLayoutManager {

    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.

image

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 CSS in the Application Model and the http://apobekiaris.blogspot.gr/2013/07/detailview-as-preview-in-gridview.html.

Always with zero code lines! and without fixed dimensions – totally relative!

image

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 Smile)!

image

This functionality is available with eXpandFramework 13.1.5.19.

Until next time,

Happy XAF’ing to all!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!