eXpress App Framework Team

May 2011 - Posts

  • KPI Module Improvements (coming in v2011 vol1)

    In order to simplify the creation of evaluating expressions for the XAF KPI we have implemented a new PropertyEditor - PopupExpressionPropertyEditor.

    Let's demonstrate its features in the FeatureCenter demo.

    To enable this editor for a property of your object, set PropertyEditorType for the Expression property of the KpiDefinition class via Model Editor.

    ModelEditor

    Then close Model Editor, navigate to KPI –> Definition, and open the "Sales" object:

    KpiDefinition

    Click the ellipsis button on the right side of the "Expression" Property Editor control. It will invoke the Expression Editor form, where you can easily compose expressions of any complexity. In FeatureCenter, we use a simple expression - let's select "Aggregate operations" and choose the "Sum()" operator.

    ExpressionEditor - Aggregate operations

    Then we select "Fields" and choose the "Freight" field.

    ExpressionEditor - Fields

    After that we can see the Sum([Freight]) expression in the corresponding KpiDefinition property.

    For now, we have PopupExpressionPropertyEditor for the Windows Forms applications only. And of course, its usage is not limited to KPI module. For instance, I saw several times that customers had calculated user-specified properties or formulas that could be modified by end-users at runtime. Expression Property Editor will come in very handy for such scenarios.

    Happy XAFingWinking smile

  • Deploying XAF to Windows Azure

    Investing in a new technology and platform such as Azure involves making decisions and taking risks. XAF and XPO are capable of running on the cloud. However it is always better to consult an expert before deciding to do so. Panagiotis Kefalidis is a Windows Azure MVP with a commercial XAF application on Azure making him a leading authority in this field.

    This webinar will focus on Azure and how to deploy XAF XCRM on it. The specific areas will include,

    1. The prerequisites for deploying a XAF application on Azure,
    2. Modification of the XCRM source code in order to simulate a multitenant system,
    3. Creating an Azure deployment package,
    4. Exploring the Azure portal,

    The XCRM will remain online and accessible after the webinar. 

    Prepare your questions and join us for this webinar on Friday 27th May 2011.

    UPDATE:
    You can view this webinar at http://www.youtube.com/watch?v=TJy0rpwofo8

  • XAF Workflow Visual Designer

    The introduction WF post has covered the basics. This is the first in a series of WF related posts, stay tuned!

    Microsoft has included a state of the art designer with WF4 which means better rehosting capabilities. The designer’s main purpose is to provide a graphical representation of business processes.  Rehosting the designer really means that it is easy to run it outside VS using our own applications.

    WF4 ships with many activities and Visual Studio 2010 includes a template for creating activities. The two most common activities used for root control flow are Sequence and Flowchart.  While any Activity can be executed as a workflow, these two provide the most common design patterns for business logic.  Below is an example of a sequential workflow designed to process an order.

    image

    However the above workflow uses native WF activities. In a XAF domain we have business objects and we need to query/modify them and let XAF shape our application accordingly. For this purpose a set of XAF specific activities have been implemented. The next image is from the XAF WF designer toolbox.

    image

    NoPersistScope

    The main purpose of this activity is to serve as a container that prevents workflow persistence. There are two cases when a workflow can be saved and unloaded. These are when the workflow server stops or is long running. In such cases the WF engine serializes the workflows in a database freeing up valuable memory. Furthermore when we think about NoPersistScope inside the XAF context, we realize that it helps to avoid session mixing or object state changing problems.

    The following image shows all this in action.

    image

    Problems can occur if there is a Delay activity inside While as the workflow will unload after the delay interval has reached. However when the While is inside a NoPersistScope this problem is avoided. The result is that the object’s state will not change and the condition will remain valid. Moreover when workflow execution reaches the Delay activity it begins to persist the workflow instance. It will also try to serialize among other variables including those containing persistent objects and a serialization error will occur. Therefore all persistent object variables should be declared within NoPersistScope to avoid serialization errors.

    ObjectSpaceTransactionScope

    This is another container and shares characteristics with NoPersistScope since it is derived from it. As the name implies this activity has the knowledge needed to construct an IObjectSpace. The complexity of the name comes first from IObjectSpace and also from the transaction process of the activity (it does not unload).

    When working with persistent objects, the recommended approach is short transactions. For example, reading data from a database, checking something, modifying, creating, deleting, committing changes and completing transactions. It is better to avoid use long running work flows when inside XPO transactions. Instead we can combine multiple ObjectSpaceTransactionScope activities in a long running workflow.

    There are several ways to specify a ConnectionString for an ObjectSpaceTransactionScope:

    1. Via its ConnectionString Property,
    2. Via a special extension in WorkflowServiceHost,
    3. Via an app.config entry,

    Our WorkflowServer class adopts the second approach by default. Upon startup IObjectSpaceProvider is passed to its constructor and is embedded as an extension for each WorkflowServiceHost object. Of course alternatives exist, we can directly access the app.config from WF designer, by simply typing ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString for example.

    IObjectSpace Activities

    Activities such as CommitChanges, RollBack, DeleteObject, GetObjectByKey, FindObjectByCriteria, GetObjectsByCriteria, CreateObject, GetObjectKey delegate to IObjectSpace original methods.

    TransactionalGetObjectSpace

    There are IObjectSpace methods that are not wrapped into an activity, but could be useful in a workflow. In such cases we need to use the TransactionalGetObjectSpace activity. Firstly we declare an IObjectSpace variable and then use this to extract it from the parent TransactionalObjectSpaceScope. After that we drop the Assign activity or InvokeMethod activity and call any method of the IObjectSpace.

    CreateObjectSpace

    The primary scenario for the CreateObjectSpace activity is to manage the need for two or more ObjectSpace objects. To work with these objects we must place them inside a NoPersistScope activity. Usually, it is unnecessary  to use the CreateObjectSpace activity except when we cannot implement our task with the help of the ObjectSpaceTransactionScope activity. As with NoPersistScope this is a low level activity.

    The ObjectSpaceTransactionScope activity works in the following way. The CreateObjectSpace activity is automatically scheduled as the first activity. The created IObjectSpace object is then placed into the Context as a named item so all the child activities have access to it.

    In the next post we will explore with XAF CRUD activities.

    We would appreciate your feedback on this post. Has it been useful to you? Feel free to contact us  with any further questions.

  • XAF - Filter Editor Improvements in List Views (coming in v2011 vol1)

    Introduction

    I would love to share some good news with our XAF customers. It all regards one of the most widely used utility control – Filter Editor. Before I start revealing this news, I would like to refresh your memory and remind you about the latest improvements we made to the control back in v2010 vol 2. As some of you may have already guessed, I am talking about an extended version of Filter Editor, allowing you to edit filters in both tree-like and text-based forms. The embedded text window, leveraging our Rich Text Editor, not only allowed end-users to type any valid criteria expression that could sometimes not be represented on a tree-like form, but also provided great filter editing capabilities, such as syntax highlighting, Intellisense and support for in-place editors. 

    Although these improvements were great and were highly appreciated by our customers, we received a lot of other feedback, from which we understood that there still were some important scenarios for which the usability of Filter Editor could be improved further. For instance, some categories of end-users may be more familiar with the tree-like editor rather than with manual typing a criterion in the text window, even if the latter has all those great editing capabilities.  This is mainly because some end-users may simply not get (should they?) criteria language syntax specifics. This is, especially, might drive them nuts in case of creating a criteria expression, involving nested object properties or collections, because such properties were not displayed on the tree-like form of Filter Editor. For instance, this is a text representation of the Contains Operator, allowing me to filter out objects, having a certain city in their collection of addresses: Addresses[City = ’Chicago’]
    Now you should see what usability problem I meant above.

    Demonstration

    I will be very short (like Gary ShortWinking smile) since now, and say that, of course, we lent ear to our customers and delivered a solution for the scenario described above. I opted to demonstrate you exactly what we have done with the help of a small video, as it seems to be worth a thousand of words:

     

    Future plans

    For now, the above improvements to Filter Editor are exclusively available for XAF customers, and work in the List View’s GridControl only. We plan to respectively enhance other XAF modules (e.g. reports, analysis, etc. - by using a unified filtering solution in ALL the places) and also support other controls and platforms in future versions of our libraries. Fortunately for us, the base logic of Filter Editor is implemented in the shared Data library, so it makes it easier and faster for us to introduce new features for other controls and platforms.

    Please let us know your feedback!

    Did you like what you have just watched? Please let us know in comments below.
    Finally, I would say that as any software, Filter Editor can be improved further, and for sure it will be improved in the future. As always, we would love to hear from you if you think that some important features are still missing, or there are scenarios that could be supported better. Thank you for your help in advance!

  • State Machine Module - Introduction

    I guess everyone is very excited by the new WF module that is coming up this year, v2011.1. However such flexibility does not come without difficulties, for example a learning curve exists. This is true even though our module provides the familiar XAF interface to deal with them. There is also a .NET4 prerequisite because as with WF4. When you do not want to face such challenges and your requirements are simple enough we recommend using our State Machine module.

    Definition:
    The StateMachine is defined by our team as module designed and positioned as a lightweight tool to easily manage the state of your object.

    Let’s do an example: Assuming we have a Task object, which can be in one of five states: Draft, NotStarted, InProgress, Paused, Completed, Droppped:

        public enum TaskStatus { Draft, NotStarted, InProgress, Paused, Completed, Dropped }

     

        [DefaultClassOptions]

        [ImageName("BO_Task")]

        public class Task : BaseObject, IStateMachineProvider {

            public Task(Session session)

                : base(session) { }

     

            public TaskStatus Status {

                get { return GetPropertyValue<TaskStatus>("Status"); }

                set { SetPropertyValue<TaskStatus>("Status", value); }

            }

     

    And we want to implement the following behavior:

    image
           Task states and transitions graph.

    In previous versions we had to implement a set of controllers and may have had to inject logic into the Task class. State Machine module provides two flexible, XAF ways of completing this task.

    Hard Coded definition

    Derive from the StateMachine<T> class, passing as a parameter the Task class. Define and link with the status the start state, and the rest of the states. Add the transitions to each state. Implement IStateMachineUISettings interface that is responsible for modifying the appearance of Task items according to a Task’s state. Under the hood the Appearance module takes over, so everything we know about it can be applied here. The same interface defines how the transition actions will be displayed using the ShowActionsInPanel property. If we choose to show them in a panel the Name property defines the label of the ActionContainer. Finally the StatePropertyName provides the property value that the StateMachine will check in order to decide the position within the transition graph.

     

        public class TaskStatusStateMachine : StateMachine<Task>, IStateMachineUISettings {

            private IState startState;

     

            public TaskStatusStateMachine() {

                startState = new State(this, TaskStatus.Draft);

                //Stage - Enum Linkage

                IState notStartedState = new State(this, "Not Started", TaskStatus.NotStarted);

                IState inProgressState = new State(this, "In Progress", TaskStatus.InProgress);

                IState pausedState = new State(this, TaskStatus.Paused);

                IState completedState = new State(this, TaskStatus.Completed);

                IState droppedState = new State(this, TaskStatus.Dropped);

     

                //Transition definition

                startState.Transitions.Add(new Transition(notStartedState));

                notStartedState.Transitions.Add(new Transition(startState));

                notStartedState.Transitions.Add(new Transition(inProgressState));

                inProgressState.Transitions.Add(new Transition(pausedState));

                inProgressState.Transitions.Add(new Transition(completedState));

                inProgressState.Transitions.Add(new Transition(droppedState));

                pausedState.Transitions.Add(new Transition(inProgressState));

     

                //Appeareance rule definition

                StateAppearance inProgressAppearance = new StateAppearance(inProgressState);

                inProgressAppearance.TargetItems = "Subject";

                inProgressAppearance.Enabled = false;

                StateAppearance completedAppearance = new StateAppearance(completedState);

                completedAppearance.TargetItems = "Subject";

                completedAppearance.Enabled = false;

                StateAppearance pausedAppearance = new StateAppearance(pausedState);

                pausedAppearance.TargetItems = "*";

                pausedAppearance.BackColor = Color.Yellow;

     

                States.Add(startState);

                States.Add(notStartedState);

                States.Add(inProgressState);

                States.Add(pausedState);

                States.Add(completedState);

            }

            //ActionContainer label

            public override string Name {

                get { return "Change status to"; }

            }

     

            public override IState StartState { get { return startState; } }

     

            public override string StatePropertyName {

                get { return "Status"; }

            }

     

            public bool ShowActionsInPanel {

                get { return true; }

            }

        }

    In order to inform the StateMachine module about state machines associated with the object Task class must implement IStateMachineProvider interface and return a set of available state machines.

        public class Task : BaseObject, IStateMachineProvider {

            public Task(Session session)

                : base(session) { }

     

            //IStateMachine members

            public IList<IStateMachine> GetStateMachines() {

                List<IStateMachine> result = new List<IStateMachine>();

                result.Add(new TaskStatusStateMachine());

                return result;

            }

     

    Runtime definition

     

    The StateMachine module contains a set of persistent objects for storing the state machine definition in the DB. Hence it is possible for the end user to modify the persistent state machine in runtime.


    The state machine definition detail view looks like this:

     

    image

     

    State definition detail view:

     

    image

     

    Controlling your transition states with code

    Thanks to the XAF architecture it is very easy to subscribe to StateMachineController events and inject your logic before or after transition execution.

        public class MyController:ViewController {

            protected override void OnActivated() {

                base.OnActivated();

                Frame.GetController<StateMachineController>().TransitionExecuting+=OnTransitionExecuting;

                Frame.GetController<StateMachineController>().TransitionExecuted+=OnTransitionExecuted;

            }

     

            void OnTransitionExecuted(object sender, ExecuteTransitionEventArgs executeTransitionEventArgs) {

     

            }

     

            void OnTransitionExecuting(object sender, ExecuteTransitionEventArgs executeTransitionEventArgs) {

     

            }

        }

    We would appreciate your feedback on this post. Has it been useful to you? Feel free to contact us  with any further questions.
  • Frameworks fan clubs in social networks

    0Some of you might have already remembered my first announcement in the XAF forum about the XAF group on Facebook. This group was initially created more than a year ago by our customer Dani Mora, whom you may know by his posts in the XAF forum. I remember there were only about 50 members at the time I joined the group about half year ago. Since then, the group has grown and it has now doubled in size (hope to double it again by the end of this year). Usually, I used this group to post the  latest news about our XAF and XPO frameworks, like blogs or interesting posts from DevExpress forums. Other users used it as yet another information source or to ask quick questions about frameworks, share opinions, experiences, etc.

    It is all cool because you can easily be in touch with real people  you are doing your every day business and whom have the same interests and goals. Communication with peers - it is  human nature after all, and that is one of the reasons Facebook and similar projects are so popular nowadays.

    A few months ago, I noticed (via Mehul friend feeds unless I am mistaken) that there is a similar group on LinkedIn – “XAF Developers”. It also existed for a long time and it was recently made public by its creator Geoff Hogan. Obviously, I continued the same activity in this group because I enjoy XAF/XPO and like communicating with customers.

    Recently, I became an Administrator in both groups (thank you to both Dani and Geoff for the collaboration!) and setup RSS feeds from our blogs (XAF, XPOmine and Tolis) so that latest news would automatically come to the group’s wall.  I am not going to stop at this stage and will certainly continue to promote and support these groups.

    So, if you are working with our frameworks (XAF or XPO) and always want to be up-to-date with them, feel free to join or like the groups below.

        Official:

        Unofficial:

    Please do not be confused by the “XAF” in the title  - XPO users are also welcome here, because XAF is based on XPO and good news for XPO fans is always good news for XAF fans as wellWinking smile

    In addition, do not forget about general DevExpress Facebook and LinkedIn groups, in case you want to be up-to-date with all DevExpress products and not only with our Frameworks.

    In case you are running or being a member of other groups or communities (related to DevExpress products) in other social networks on the Web or in other places, feel free to contact Rachel Hawley, Community Evangelist at DevExpress or email us at management@devexpress.com directly.

  • How to implement a custom EasyTest command

    Related Posts:
    Thinking of Testing… Enter EasyTest
    How to run EasyTest scripts from code
    EasyTest and test data

    EasyTest has a set of predefined commands that cover most of the use cases. To support the remaining cases there is a very easy way to write custom commands. Our Code Central already has a sample demonstrating that and today I am going to step through it.

    Lets assume we have a Product with PublishDate and Status properties. The Status property can take the following values: Valid, Invalid. We want to test the following logic: If PublishDate is less than the system date + 10 days then the Status must have a value of “Invalid”. If we write the code we will end up with something like (using the new

    *FillForm
    
     Department = Sales Department

    The test will pass if we run it today. However if we try to run it after a few days it will fail (because the Published date is fixed the Status will become Valid).

    To overcome the problem we can create a custom command. All commands have to derive from DevExpress.EasyTest.Framework.Command class and implement the InternalExecute method. You probably find it easier to write the script in pseudo code before implementing the InternalExecute method .

    #DropDB AdditionalCommandsEasyTest 
    
    #Application AdditionalCommandsWin
    
    *Action Navigation(Product) 
    
    *Action New 
    
    ;---Custom Command 
    
    *FillDateTimeValue Publish 
    
     Culture = en-US 
    
     Days = -1 
    
     Hours = 1 
    
    ;---End Custom Command 
    
    *Action Save And Close 
    
    *CheckTable Product 
    
     Columns = Status 
    
     Row = Invalid

    As you see we have replaced the FillForm command with our custom FillDateTimeValue command and we have used 4 parameters. The main parameter is the name of the property, which is followed by Culture parameter along with Days and Hours to overcome any localization problems.

    Now we are ready to implement our custom command.

        public class FillDateTimeValueCommand : Command{

            private int GetIntegerParameterValue(string parameterName) {

                int result = 0;

                Parameter parameter = Parameters[parameterName];

                if (parameter != null){

                    if(!Int32.TryParse(parameter.Value, out result)) {

                        throw new CommandException(string.Format("'{0}' value is incorrect", parameterName), StartPosition);

                    }

                }

                return result;

            }

            protected override void InternalExecute(ICommandAdapter adapter){

                //Get the parameters

                int deltaDays = GetIntegerParameterValue("Days");

                int deltaHours = GetIntegerParameterValue("Hours");

                int deltaMinutes = GetIntegerParameterValue("Minutes");

                CultureInfo currentCulture = GetCurrentCulture();

                string fieldName = Parameters.MainParameter.Value;

                //Create the DataTime values

                string dateTimeValue = GetDateTimeValue(deltaDays, deltaHours, deltaMinutes, currentCulture);

                //Get The control

                ITestControl testControl = adapter.CreateTestControl(TestControlType.Field, fieldName);

                //Apply the datetime value

                testControl.GetInterface<IControlText>().Text = dateTimeValue;

            }

     

            CultureInfo GetCurrentCulture() {

                string cultureName = Parameters["Culture"] != null ? Parameters["Culture"].Value : null;

                return cultureName != null ? CultureInfo.GetCultureInfo(cultureName) : null;

            }

     

            string GetDateTimeValue(int deltaDays, int deltaHours, int deltaMinutes, CultureInfo currentCulture) {

                DateTime dateTime = DateTime.Now.Add(new TimeSpan(deltaDays, deltaHours, deltaMinutes, 0));

                return currentCulture != null ? dateTime.ToString(currentCulture) : dateTime.ToString();

            }

        }

     

    The InternalExecute method  implementation is very simple. First we get the parameters out of ParametersList collection and calculate the DateTime value according to our logic. The CreateTestControl method finds the original control by its caption and wraps it around an ITestControl interface. To communicate with the control the ITestControl provides the generic GetInterface<T> method.

     

    The supported interfaces for the method live inside EasyTest namespace.

     

    image

     

    It is very rare but it is possible to extend an ITestControl with more interfaces. But that is another blog subject.

     

    Register the command

    In order EasyTest to recognize the FillDateTimeValueCommand we need to register it. The WinAdapter, WebAdapter classes are responsible for running the application under test. They are also handling the initialization of the CommandAdapter hence its the right place to register our custom commands. What we need is to to replace the default WinAdapter, WebAdapter classes. This can be accomplished by creating a new Class Library project and adding a class like this:

     

        public class ExtendedWinAdapter : WinAdapter{

            public override void RegisterCommands(DevExpress.EasyTest.Framework.IRegisterCommand registrator){

                base.RegisterCommands(registrator);

                registrator.RegisterCommand("FillDateTimeValue", typeof(FillDateTimeValueCommand));

            }

        }

     

    In the same project open AssemblyInfo.cs and register the adapter there as well.

     

    [assembly: Adapter(typeof(ExtendedAdapters.ExtendedWinAdapter))]

     

    Finally modify your scripts Config.xml to point to the new adapter.

     

    image

     

    Debugging the command

    a) Set as StartUp project the one that contains the command

    image
    b) Set the breakpoints in the code 

    image


    c) Open project properties and modify the (Start external program/Command line arguments) under debug tab. The first should point to your TestExecutor executable the latter to the path of the script that uses the command.

     

     

     

    That’s it for the custom commands! What do you think? Should we go through couple of more examples?

  • XAF – Finally…Workflow It! (coming in v2011 vol1)

    Introduction

    We are happy to announce that in v2011 vol1 version of the eXpressApp Framework, we have finally supported popular workflow scenarios, starting from simple and finishing by very complex ones. In this introduction blog post, we will dig into what actually workflow is and describe what we implemented in our framework for its support.

    workflow

    Windows Workflow Foundation 4.0 support!

    As part of workflow integration in XAF, we supported Windows Workflow Foundation 4.0 (WWF).  Briefly, it provides “a programming model, in-process workflow engine and rehostable designer to implement long-running processes as workflows within .NET applications”.

    Fortunately for us, Microsoft already did a lot describing main benefits of their technology to developers, and we can use these ready materials to easily introduce you to workflow integration in our product.

    I suggest you get started with understanding the “workflow way” from the following article:

    and then continue with the following ones:

    , describing specifics of the WWF technology itself.

     

    In these links, you will learn that you can use WWF to model in a declarative way complex business scenarios by creating Activities in a visual designer. Activities are functionality parts of different scales: from the atomic to entire business process descriptions. The .NET Framework provides a library of activities (such as WriteLine, an activity that writes text to the console or other form of output). Custom activities can also be developed for additional functionality, and a lot more!

    Primary supported WWF scenarios

    Our primary goals while implementing workflow integration in XAF were the following:

    • provide an easy way to model business process as activities and workflows;
    • provide access to XPO business objects from Workflow Activities, including support for CRUD operations;
    • host workflows and run them for certain objects;
    • persist long running workflows in the XPO database;
    • track and control states of running workflows.

    We plan to describe these aspects in greater detail in the next blog series:

    • XAF Workflow Visual Designer;
    • Working with XAF CRUD activities;
    • Hosting and launching workflows;
    • XAF Workflow persistence storage;
    • Tracking workflows;
    • Reusing custom activities;

    If you would like us to cover more scenarios, or want to confirm whether certain tasks are appropriate for using WWF, please let us know in the comments to this blog. We will be happy to hear your thoughts.

    Support for lightweight workflow scenarios

    Usually, Windows Workflow Foundation is worth using in complex scenarios, e.g. documents flow control in an enterprise. So, it may often require configuring a server for workflow hosting. It adds unwanted complexity, and hence, it may not fit well in simple scenarios, such as objects status management. Of course, WWF still can be used for such scenarios, but it will be like lighting a cigarette from a nuclear reactor, if you see what I mean.

    For the latter scenario (objects status management) it is more suitable to use the lightweight State Machine module, which is also coming in 11.1. Getting started with this module will not consume considerable resources, including learning the WWF technology. That is very important for small businesses, and so it makes the State Machine module a good alternative to WWF. We will describe this new module in a future blog.

    Why is this feature useful for you?

    Like any feature or technology, it requires understanding what it is, why it is useful, and when it makes sense to use it. Let us start with a brief description of “workflow” from Wikipedia:

    “A workflow consists of a sequence of connected steps. It is a depiction of a sequence of operations, declared as work of a person, a group of persons, an organization of staff, or one or more simple or complex mechanisms. Workflow may be seen as any abstraction of real work. For control purposes, workflow may be a view on real work under a chosen aspect, thus serving as a virtual representation of actual work. The flow being described may refer to a document or product that is being transferred from one step to another.

    A workflow is a model to represent real work for further assessment, e.g., for describing a reliably repeatable sequence of operations. More abstractly, a workflow is a pattern of activity enabled by a systematic organization of resources, defined roles and mass, energy and information flows, into a work process that can be documented and learned. Workflows are designed to achieve processing intents of some sort, such as physical transformation, service provision, or information processing.”

    As you see, almost everything you can do in your business applications “breathes” with workflows. For better understanding, I would prefer to get away from this abstract and scientific description, and stay with something more concrete. Let’s demonstrate how to implement a simple incoming issues process scenario. So, here we go.

    Suppose we have two classes: Issue and Task. A new task should be created when an active issue appears. It is necessary to inform an end user that there is work to do. The Issue and Task classes are very simple:

      1: [DefaultClassOptions]
    
      2: [DefaultProperty("Subject")]
    
      3: public class Issue : BaseObject {
    
      4:     private string subject;
    
      5:     private bool active;
    
      6:     public Issue(Session session) : base(session) { }
    
      7:     public string Subject {
    
      8:         get { return subject; }
    
      9:         set { SetPropertyValue("Subject", ref subject, value); }
    
     10:     }
    
     11:     public bool Active {
    
     12:         get { return active; }
    
     13:         set { SetPropertyValue("Active", ref active, value); }
    
     14:     }
    
     15: }
    
     16: [DefaultClassOptions]
    
     17: public class Task : BaseObject {
    
     18:     private string subject;
    
     19:     private Issue issue;
    
     20:     public Task(Session session) : base(session) { }
    
     21:     public string Subject {
    
     22:         get { return subject; }
    
     23:         set { SetPropertyValue("Subject", ref subject, value); }
    
     24:     }
    
     25:     public Issue Issue {
    
     26:         get { return issue; }
    
     27:         set { SetPropertyValue("Issue", ref issue, value); }
    
     28:     }
    
     29: }

    Previously, we would implement some hard-coded business logic, but now, with the help of the Windows Workflow Foundation functionality, we can easily get the same or better result using the Workflow Designer and the built-in DevExpress.Workflow.Activities library.

    First, we will add a Class Library for our custom workflow activities (it is WorkflowDemo.Activities in the screenshot below). Since Issue and Task are pure persistent objects, and we want to perform CRUD operations with them, we need to add a reference to DevExpress.Workflow.Activities, which contains appropriate activities.

    We will add a new Activity into our WorkflowDemo.Activities project and name it CreateUserTask and then construct it using the designer:

    01 - CreateUserTask

    As you can see in screenshot above, there is an ObjectSpaceTransactionScope activity, containing several activities. This activity encapsulates a data access layer (including ConnectionString) and blocks for workflow persisting. The reason for the persistence block is that persistent objects most often are not serializable.

    The first activity inside the ObjectSpaceTransactionScope is a TransactionalCreateObject<Task> activity that creates a new Task object.

    Then, the TransactionalGetObjectByKey<Issue> activity loads an issue with a passed Id from a database.

    Next, two Assign activities set the Issue and Subject properties of the created Task.

    The TransactionalCommitChanges activity commits all the changes we made in the database.

    And the last Assign activity sets the CreatedTaskId result parameter.

    Also, we created several auxiliary variables and arguments that were used to pass data between activities.

    Now, we have designed a workflow activity that implements simplified incoming issues processing, and can run it as a typical workflow. For example, we can publish it as a WCF service, add a controller with the "Start 'Process Issue' workflow" action into a WorkflowDemo.Module project and simply make a call to the published service. Another approach is to start the workflow automatically when a new Issue appears: it could be implemented via a separate application that periodically searches issues by the "Active = true" criteria and start the workflow for each issue found.

    Now, if we create new active Issue in the client application, CreateUserTask activity will create a new Task for that issue:

    2

    Since the main workflow scenario will be creation of activities at runtime, we also provided a runtime workflow designer that can be invoked in every XAF Windows Forms application:

     

    3

    4

    We also supported scenarios such as automatic database scanning for workflow target objects (see the 'Target Object Type' and 'Criteria' properties on the Workflow definition Detail View) and made it possible to implement a workflow server that can receive requests to start workflows, load workflow definitions from the database, etc.

    Related Links

    Online documentation
    How-to & Blog posts

    Looking forward to your feedback!

    We know that a lot of XAFers were longing for this feature for many years, and so we are eager to receive your feedback.

    Do you like this feature?
    Do you see a place for it in your XAF arsenal?

    Please tell us your thoughts in comments to this blog!

    Happy XAFing!Winking smile

  • XAF–Ask the Team Webinar Today!

    Yes folks, it’s that time of the month again! The time where all you good XAF programmers out there can role up to our webinar and ask me and the team any question about XAF that’s on your mind. Details of this month’s webinar can be found here, where you’ll get country specific details as to the timing.

    As an extra special treat this evening, our brand new XAF evangelist, Tolis, will be joining us. Tolis has a wealth of real life experience using XAF, and so this is a perfect opportunity to ask him questions about using XAF in the wild, as opposed to specific feature questions. Of course, you are still welcome to ask those too!

    Anyway, the team and I are really looking forward to speaking to you this evening, so pop along and register now!

    ‘til this evening then, happy XAF’ing! Smile

  • How to write EasyTests in code

    Related blogs:
    Thinking of Testing… Enter EasyTest
    How to implement a custom EasyTest command
    EasyTest and test data

    EasyTest language contains less syntactic sugar, so why should I care writing my scripts in C# or VB.Net?

    I will answer using the words of our good customer Martin Brekhof: The main idea is that when I want to run the same test with (a lot off) different field values using for example a database that this would be easier to accomplish using C#. Since last week i could also see the benefit of something like a report tool for these tests (putting the testresults in a database and use a XAF app to create reports etc.).

    Another customer (good too!!) Robert Anderson shared his implementation of an EasyTest script parser and pointed: running EasyTests from code allows our testers to create their own tests which can be integrated into the build process with zero efforts

    [Test]
    public void UserTest_ChangePasswordOnFirstLogon()
    {
        RunTest(@"UserTests\ChangePasswordOnFirstLogon.ets");
    }

    The full discussion is available on this forum thread

    However EasyTest architecture is flexible enough to allow writing tests from code without having to parse or create (*.ets) files.

    Our team has prepared an example that demonstrates how to create EasyTest scripts in C#. The sample application is based on the Contact and DemoTask business classes taken from MainDemo. An in-memory data store is used for testing, which is performed via NUnit.

    Writing tests

    The test methods are located in the CommonTests, WebTests and WinTests classes. These classes are derived from a custom base class, providing auxiliary functionality, such as starting and stopping the application for testing. If you look at the test methods, you will see that they look like regular unit tests with lots of ‘Asserts’.  Basically, these tests consist of three different types of actions.
    Firstly, you can invoke the adapter.CreateControl method to access application controls. After you get a control, you can query its status via the control.GetInterface method. You will need to specify a valid interface as the method parameter. Most of these interfaces reside in the DevExpress.EasyTest.Framework namespace and begin with ‘IControl’.


    image

    After you have cast the retrieved control to an interface this way, you can check specific properties, such as Enabled, or invoke methods, such as GetRowCount.
    Secondly, you can use commandAdapter to execute basic commands, such as SetFieldValue. The commandAdapter is an instance of the TestCommandAdapter class, which wraps basic EasyTest commands. This class is implemented in the TestCommandAdapter file. Thirdly, you can use the NUnit.Framework.Assert method to check whether a test is passed correctly. Here is a sample test :

            public void ChangeContactNameTest_() {

                ITestControl control = adapter.CreateTestControl(TestControlType.Table, "");

                IGridBase table = control.GetInterface<IGridBase>();

                Assert.AreEqual(2, table.GetRowCount());

     

                List<IGridColumn> columns = new List<IGridColumn>(table.Columns);

                IGridColumn column = commandAdapter.GetColumn(control, "Full Name");

     

                Assert.AreEqual("John Nilsen", table.GetCellValue(0, column));

                Assert.AreEqual("Mary Tellitson", table.GetCellValue(1, column));

     

                commandAdapter.ProcessRecord("Contact", new string[] { "Full Name" }, new string[] { "Mary Tellitson" }, "");

     

                Assert.AreEqual("Mary Tellitson", commandAdapter.GetFieldValue("Full Name"));

                Assert.AreEqual("Development Department", commandAdapter.GetFieldValue("Department"));

                Assert.AreEqual("Manager", commandAdapter.GetFieldValue("Position"));

     

                commandAdapter.DoAction("Edit", null);

     

                commandAdapter.SetFieldValue("First Name", "User_1");

                commandAdapter.SetFieldValue("Last Name", "User_2");

     

                commandAdapter.SetFieldValue("Position", "Developer");

     

                commandAdapter.DoAction("Save", null);

     

                Assert.AreEqual("User_1 User_2", commandAdapter.GetFieldValue("Full Name"));

                Assert.AreEqual("Developer", commandAdapter.GetFieldValue("Position"));

            }


    This example can be modified to be used with any unit-testing engine and can be extended with additional commands by introducing new methods in the TestCommandAdapter class. You can also modify the command syntax by renaming and modifying TestCommandAdapter‘s methods.
    The sample application uses active directory authentication, and we kill the application instance and start a new one between tests. If you are using standard authentication, you can reuse the application instance by logging off the application instead of killing it. To do this, modify the TearDown method of the WinEasyTestFixtureHelperBase/WebEasyTestFixtureHelperBase.


    Preparing your application for testing

    To use the EasyTest.Tests assembly in your application, you will need to modify the TestApplcationTests.cs file and specify correct paths to the application on your local hard drive. Usually tests need particular test data. And, since they should also be atomic, we recommend you back up the test data between different test runs. For this purpose, modify the DatabaseVersionMismatch event handler as follows:

    #if EASYTEST

                e.Updater.Update();

                e.Handled = true;

                TestApplication.EasyTest.InMemoryDataStoreProvider.Save();

    #else

    ...

    #endif

    The ASP.NET application should restore this data when a test is finished, and so you should handle the AcquireRequestState event (See sample Global.asax)

    #if EASYTEST

            protected void Application_AcquireRequestState(Object sender, EventArgs e) {

                if(HttpContext.Current.Request.Params["Reset"] == "true") {

                    TestApplication.EasyTest.InMemoryDataStoreProvider.Reload();

                    WebApplication.Instance.LogOff();

                    WebApplication.Redirect(Request.RawUrl.Replace("&Reset=true", "").Replace("?Reset=true", ""), true);

                }

            }

    #endif

    We also recommend you reload the data store provider in the Session_Start event handler:

            protected void Session_Start(Object sender, EventArgs e) {

    #if EASYTEST

                TestApplication.EasyTest.InMemoryDataStoreProvider.Reload();

    #endif

                WebApplication application = new TestApplicationAspNetApplication();

                WebApplication.SetInstance(Session, application);

     

    #if EASYTEST

                application.ConnectionString = "XpoProvider=InMemoryDataSet";

    #else

                if(ConfigurationManager.ConnectionStrings["ConnectionString"] != null) {

                    WebApplication.Instance.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

                }

    #endif

                WebApplication.Instance.Setup();

                WebApplication.Instance.Start();

            }

     

    The following steps are required for a Windows Forms application, too (Program.cs file).
    a) Register the data store provider by calling the Register method of the InMemoryDataStoreProvider class:

            [STAThread]

            static void Main() {

    #if EASYTEST

                DevExpress.ExpressApp.EasyTest.WinAdapter.RemotingRegistration.Register(4100);

                TestApplication.EasyTest.InMemoryDataStoreProvider.Register();

    #endif

     
    b) Modify the application connection string to use the in-memory data store provider:

    #if EASYTEST

                winApplication.ConnectionString = "XpoProvider=InMemoryDataSet";

    #else

                if(ConfigurationManager.ConnectionStrings["ConnectionString"] != null) {

                    winApplication.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

                }

    #endif

    That was a full description of how the sample is designed. We would like to hear your thoughts about this approach for testing XAF applications.

    Happy EasyTesting!

    EasyTestsViaNUnit.zip

LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners