Creating a State Machine module for eXpandFramework – Part 1

XAF Team Blog
08 August 2011

Let me describe for a moment how we at DevExpress work. We build and sell software which means that we only sell and provide support for products that have been built and tested by us! However I am here as a framework evangelist and huge XAF fan. This makes it my duty to spread the word as much as I can and make XAF even bigger. To this end through collaboration within the XAF community, we have been building and supporting eXpand. This framework follows XAF to the letter and takes things even further. eXpand gets its inspiration from real life situations and bases itself on examples from DevExpress Support Center. eXpand is the first open source project based on the DevExpress eXpressApp Framework (XAF). More info is available at www.expandframework.com and our very existence relies on your efforts! Anyone is welcome to contribute and enjoy the rewards. It is not necessary to be a XAF guru, we can all manage to create a behavior taken from DevExpress code central. Let’s work together to enhance our beloved XAF!

Today we are going to deal with creating a reusable module for eXpandFramework. This new module will host State Machine logic therefore we are going to name it Xpand.ExpressApp.StateMachine. A Security logic similar to this is going to be the first resident however we are going to use permissions rather than manually writing the custom function criteria operator to the TargetObjectCriteria. This module can be used as a standalone without the need of eXpandFramwork for the moment. However keeping it under the eXpand umbrella will help us to share features/processes within the framework in the future.

First we need to create a new VS project that shares characteristics with the rest of the eXpand modules. Currently eXpand only provides a new Solution VS template. This means that we need to do things the old fashioned way, i.e. copying and pasting an existing module. Our behavior is platform independent thus it’s a good idea to choose a module that is also platform independent such as Xpand.ExpressApp.ViewVariants.

The next step is to open the cloned project, rename it and set its references to those shown in the pic below. It is important to leave all Xpand core references as they are.

image

It is advisable to register the original StateMachine module to avoid having to register it later.

image

XpandSystemModule is already registered since we used the Xpand.ExpressApp.ViewVarians project as a template.

We want (with the help of the existing XAF Security permissions system) to be able to assign special types of permissions to a role. We can then use these permissions to control the transition to certain states. The State Machine module uses XpoStateMachine and XpoState persistent classes. These classes can be linked to a permission by name. As a result a permission having 2 properties StateMachineName and StateName would be sufficient.

[NonPersistent]

public class StateMachineTransitionPermission : PermissionBase {

    public override IPermission Copy() {

        return new StateMachineTransitionPermission(Modifier, StateCaption, StateMachineName);

    }

 

    public StateMachineTransitionPermission() {

    }

    public override SecurityElement ToXml() {

        SecurityElement result = base.ToXml();

        result.AddAttribute("modifier", Modifier.ToString());

        result.AddAttribute("stateMachineName", StateMachineName);

        result.AddAttribute("stateCaption", StateCaption);

        return result;

    }

    public override void FromXml(SecurityElement e) {

        base.FromXml(e);

        Modifier =

            (StateMachineTransitionModifier)

            Enum.Parse(typeof(StateMachineTransitionModifier), e.Attributes["modifier"].ToString());

        StateCaption = e.Attributes["stateCaption"].ToString();

        StateMachineName = e.Attributes["stateMachineName"].ToString();

 

    }

    public StateMachineTransitionPermission(StateMachineTransitionModifier modifier, string stateCaption, string stateMachineName) {

        Modifier = modifier;

        StateCaption = stateCaption;

        StateMachineName = stateMachineName;

    }

    public override bool IsSubsetOf(IPermission target) {

        var isSubsetOf = base.IsSubsetOf(target);

        if (isSubsetOf) {

            var stateMachineTransitionPermission = ((StateMachineTransitionPermission)target);

            return stateMachineTransitionPermission.StateCaption == StateCaption &&

                   stateMachineTransitionPermission.StateMachineName == StateMachineName;

        }

        return false;

    }

 

    public StateMachineTransitionModifier Modifier { get; set; }

 

    public string StateMachineName { get; set; }

 

    public string StateCaption { get; set; }

}

 

A  Modifier property can be used to disable our permission, moreover the SecuritySystem is going to grant the permission by calling the IsSubsetOf method.

Finally, we are going to create a controller and check if a permission with the same state and statemachine name has been granted to our system. If not we are going to throw an

exception

 

public class StatePermissionController : ViewController, IModelExtender {

    void IModelExtender.ExtendModelInterfaces(ModelInterfaceExtenders extenders) {

        extenders.Add<IModelOptions, IModelOptionsStateMachine>();

    }

 

    protected override void OnActivated() {

        base.OnActivated();

        var stateMachineController = Frame.GetController<StateMachineController>();

        stateMachineController.TransitionExecuting += OnTransitionExecuting;

    }

 

    void OnTransitionExecuting(object sender, ExecuteTransitionEventArgs executeTransitionEventArgs) {

        var states = executeTransitionEventArgs.StateMachine.States.OfType<XpoState>();

        foreach (var state in states) {

            if (IsNotGranted(state))

                throw new UserFriendlyException("Permissions are not granted for transitioning to the " + state.Caption);

        }

    }

 

    bool IsNotGranted(XpoState state) {

        return IsNotGranted(new StateMachineTransitionPermission(StateMachineTransitionModifier.Deny, state.Caption, state.StateMachine.Name));

    }

 

    static bool IsNotGranted(IPermission permission) {

        var securityComplex = ((SecurityBase)SecuritySystem.Instance);

        bool isGrantedForNonExistentPermission = securityComplex.IsGrantedForNonExistentPermission;

        securityComplex.IsGrantedForNonExistentPermission = true;

        bool granted = SecuritySystem.IsGranted(permission);

        securityComplex.IsGrantedForNonExistentPermission = isGrantedForNonExistentPermission;

        return granted;

    }

}

During the writing of this post M. Brekhof asked if it is possible to hide the transition in the UI if there are no permissions. This is a certainly a useful feature to include in the new module. To implement it we can subscribe to the ItemsChanged event of the ChangeStateAction and use the IsGranted method there.

image

Using the model to control the above allows us more flexibility as we can choose whether to include these improvements or not. In order to do so we need to define and register a model extender.

image

It should be clear that this is a rapid development! In a few short minutes we have created a module which can be used as often as necessary to enhance the functionality of our applications. This is another fine example of getting the job done the XAF way!

So far we have not discussed how to create lookups for our StateMachineTransitionPermission StateMachineName,StateName properties. Don’t worry all of this will be featured in part 2. In the meantime if any of you need any other information please let me know so that I can cover it too.

Happy eXpanding!

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.