Παρουσίαση με Ετικέτες

Creating a State Machine module for eXpand Framework–Part 2
23 Σεπτεμβρίου 11 03:52 πμ | tolisss | 0 σχόλια   

Prerequisites
Part 1

In this post we are going to enhance the State Machine module UI. Remember that along with all the usual XAF goodies we can now use Xpand code base which gives us a lot more options. Our StateMachineTransitionPermission has 2 lookups, StateMachineName and StateMachine. Our goal is to populate both of these cascading lookups without creating a platform specific module.

StateMachineNames

Creating lookups is a common scenario for which Xpand provides a set of property editors and controllers. By contrast with other business frameworks XAF allows maximum flexibility. Therefore in most cases we are able to code in such a generic way that everything could live in separate frameworks such as eXpand. Now, in order to populate the StateMachine name I am going to derive a new controller from a specialized abstract controller which is Xpand.ExpressApp.SystemModule.PopulateController<T>. This controller uses the PredefinedValues attribute of the XAF model. When filling the attribute with a set of values separated by semicolons XAF will create a lookup with these values targeting each supported platform.

image

However, if at runtime we set the value of the PredefinedValues attribute this will be written at model’s lastlayer and it will make it dirty. We want to avoid this because we want to leave the lastlayer intact.. To cater for this need the populate controller uses a hack. First it stores the lastlayer in a variable then removes it from the model’s layers collection. As a result it is possible to modify the new lastlayer as shown in the Populate method and then return the clean old one to its place. Now the model has all the necessary information with a clean userdiffs layer and while XAF is creating a new view can get the PredefinedValues string from it and create the lookups.

public abstract class PopulateController<T> : ViewController<ObjectView> {

   

    …

 

    protected virtual void Populate(Func<IModelMember, string> collect) {

        var name = PropertyName;

        if (name != null) {

            var model = ((ModelApplicationBase)Application.Model);

            var lastLayer = model.LastLayer;

            model.RemoveLayer(lastLayer);

            PopulateCore(collect, name);

            model.AddLayer(lastLayer);

       }

   }

 

    private void PopulateCore(Func<IModelMember, string> collect, string propertyName) {

        IModelMember modelMember = View.Model.ModelClass.AllMembers.FirstOrDefault(member => member.Name == propertyName);

        if (modelMember != null) {

            modelMember.PredefinedValues = collect.Invoke(modelMember);

        }

    }

    …

    …

   }

 

Although this seems like a complicated explanation users need not be intimidated! The implementation of our controller that will populate all StateMachineNames is as simple as this,

public class StateMachinePopulateController : PopulateController<StateMachineTransitionPermission> {

    protected override string GetPredefinedValues(IModelMember wrapper) {

        IList<XpoStateMachine> xpoStateMachines = ObjectSpace.GetObjects<XpoStateMachine>(null);

        return xpoStateMachines.Select(machine => machine.Name).AggregateWith(";");

    }

 

    protected override Expression<Func<StateMachineTransitionPermission, object>> GetPropertyName() {

        return permission => permission.StateMachineName;

    }

}

 

The first thing we did was to provide the propertyName in the GetPropertyName method. Then using the GetPredifinedalues method we return the semincolon delimited string with the machine names. This very simple controller is capable of populating the statemachine lookup for win and web platforms!.
 
StateCaptions
This is a cascading lookup and as a result when the current StateMachineName changes it needs to provide a list of all its StateCaptions. To this end we are going to use a specialized property editor, Xpand’s StringLookupEditor. This supports the DataSourceProperty XAF attribute which will be used to provide the StateCaption collection. Moreover when using Xpand it is possible to mark editors with an interface and host it in a transparent module. We can then use the Xpand PropertyEditor attribute with the type of the interface as parameter to tell XAF which propertyeditor will be created at runtime. Finally we need to apply all these along with an ImmediatePostData to the StateMachineName property. The permission will look like this,
 

[ImmediatePostData]

public string StateMachineName { get; set; }

 

//IStringLookupPropertyEditor lives in Xpand.ExpressApp assembly

//Xpand.ExpressApp.Web.PropertyEditors.StringLookupPropertyEditor, Xpand.ExpressApp.Win.PropertyEditors.StringLookupPropertyEditor inherit from IStringLookupPropertyEditor

[PropertyEditor(typeof(IStringLookupPropertyEditor))]

[DataSourceProperty("StateCaptions")]

public string StateCaption { get; set; }

 

IList<string> _stateCaptions = new List<string>();

[Browsable(false)]

public IList<string> StateCaptions {get {return _stateCaptions;}}

 

If you look carefully at this code however you may notice that __stateCaptions count is always zero. Let me remind you here that the StateMachineTransitionPermission is a non persistent sessionless object. This means that the object is not handled by an ObjectSpace therefore a call like ObjectSpace.FindObjectSpaceByObject(this) will always return null. In addition the permission does not implement INotifyPropertyChanged so we need to synchronize the class just before the StateCaptions are requested. Below you can see a modified version of the StateMachinePopulateController,

 

public class StateMachinePopulateController : PopulateController<StateMachineTransitionPermission> {

    protected override void OnViewControlsCreated() {

        base.OnViewControlsCreated();

        var stringLookupPropertyEditor = GetPropertyEditor(permission => permission.StateCaption) as IStringLookupPropertyEditor;

        if (stringLookupPropertyEditor != null)

            stringLookupPropertyEditor.ItemsCalculating += StringLookupPropertyEditorOnItemsCalculating;

    }

    void StringLookupPropertyEditorOnItemsCalculating(object sender, HandledEventArgs handledEventArgs) {

        var propertyEditor = GetPropertyEditor(permission => permission.StateMachineName);

        if (propertyEditor != null && View.IsControlCreated) {

            var stateMachineTransitionPermission = ((StateMachineTransitionPermission)View.CurrentObject);

            var readOnlyCollection = GetStateCaptions(propertyEditor);

            stateMachineTransitionPermission.SyncStateCaptions(readOnlyCollection, propertyEditor.ControlValue as string);

        }

    }

 

    ReadOnlyCollection<string> GetStateCaptions(PropertyEditor propertyEditor) {

        var stateMachineName = propertyEditor.ControlValue as string;

        return ObjectSpace.GetObjects<XpoState>(state => state.StateMachine.Name == stateMachineName).Select(

                state => state.Caption).ToList().AsReadOnly();

    }

 

Finally we add the new SyncStateCaptions method and the full version of the permission will be,

 

[NonPersistent]

public class StateMachineTransitionPermission : PermissionBase {

    …

    …

 

    [ImmediatePostData]

    public string StateMachineName { get; set; }

 

    [PropertyEditor(typeof(IStringLookupPropertyEditor))]

    [DataSourceProperty("StateCaptions")]

    public string StateCaption { get; set; }

 

    IList<string> _stateCaptions = new List<string>();

    [Browsable(false)]

    public IList<string> StateCaptions {get {return _stateCaptions;}}

 

    public void SyncStateCaptions(IList<string> stateCaptions, string machineName) {

        StateMachineName = machineName;

       _stateCaptions = stateCaptions;

    }

}

To support platform independent cascading lookups we wrote only about 10 lines of code! This is proof of how much XAF architecture cuts down on development costs. The module can be downloaded from the Xpand download page   and we are happy to hear your feedback. Remember that your questions are the best candidates for future posts!


Δημοσίευση στην κατηγορία: , ,
Creating a State Machine module for eXpandFramework – Part 1
08 Αυγούστου 11 03:19 μμ | tolisss | 0 σχόλια   

Let me describe for a moment how we at DX 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 DX 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!

eXpand is the first open source project based on the DevExpress eXpressApp Framework (XAF). More info is available at www.expandframework.com


Δημοσίευση στην κατηγορία: , ,

Search

Go

Το Ιστολόγιο

Ιστορικό Δημοσιεύσεων

Συνδρομές