Tuesday, December 27, 2011

LINQPad is XPO’s new best friend!

LINQ is .NET Language-Integrated Query. It's included in .NET Framework 3.5 and you can use it in Visual Studio 2008 projects. XPO has officially supported LINQ since v7.3 and since then we have continued to improve and support it. Nowadays LINQ to XPO is very mature, extendable (How to: Implement Custom Functions and Criteria in LINQ to XPO) and powerful (Free Joins). In the latest version we make it even easier to use LINQ to XPO since we only distribute one assembly, DevExpress.XPO.dll, which includes everything! (custom providers as well).

XPO can talk transparently to a large list of database systems. It was already possible to make complex queries using our standard criteria syntax, however using LINQ offers some great advantages.

  1. No magic strings, like you have in standard criteria syntax,
  2. Intellisense support making it easier and faster to construct your queries,
  3. Compile time checking,
  4. Learning to write LINQ queries/lambdas is a must learn for .NET developers,

The benefits are clear, however there is a caveat when using LINQ. It is not possible to use Edit and Continue and with even the smallest change you need to restart your debugging session. As a result your development speed is decreased dramatically.

The solution as always is to use the right tool and in this case the right tool is LINQPad. It provides a flexible UI allowing you to use LINQ in various ways.

In our latest version our team released an XPO context driver for LINQPad. Let’s see how to configure it.

Firstly we need to install the driver found at C:\DevExpress 2011.2\Components\Tools\DXperience\XPOContextDriver.lpx.

image

Secondly we need to setup a new connection.

image

In this example we are going to use the MainDemo assembly containing the business objects.

image

LINQPad populated the domain in a hierarchical treelist. This allows us to enjoy using drag & drop plus intellisence in its expression editor to form the LINQ queries. Furthermore it is possible to use your style of language as demonstrated below.

image

The cool part is that LINQPad, written by the great Joe Albahari, is available for free. It is strongly recommended as a great way to learn LINQ.

Now for the fun part, let’s see it in action. Imagine we have the following code in our VS.

var queryable = from c in contacts

                let tasks = c.Tasks.Where(t => t.ActualWork < 0)

                where tasks.Any()

                orderby c.LastName

                select new {

                               c.LastName,

                               Orders = tasks.Count(),

                           };

 

We wish to see the results of queryable variable ,the best way to achieve this is to set a breakpoint then hit F5. Next we wait for our app to load and navigate to the action that will hit the breakpoint. Finally we must select the queryable variable and hit Shift+F9 to invoke the debugger window and examine the result set. Now we can try to make our requirements more complex. Lets say we also want to examine the result set when ActualWork>10. The solution is really easy when using LINQPad, we simply need to copy paste the code from VS to LINQPad the expression editor like this,

image

After this we can quickly and easily form queries as we wish and examine their result sets instantly. Using the excellent and cheap LINQPad Autocompletion it is possible to use VS like intelligence to further speed up your development.

image

PS: eXpandFrameWork contributors are already using the Autocompletion feature since LINQPad offered a license for its contributors.

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

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Sunday, December 25, 2011

An eXpansive Christmas!

Just a quick post to wish all of you out there in the community a very Merry Christmas and a Happy New Year! I thought I had better take some time out from all the usual revelry to thank you all for making our framework what it is right now. Now is the season of generosity and goodwill so it's a good time to reflect on what has been achieved at eXpand through the giving of ideas. Of course my biggest Christmas wish is that communication and collaboration between us all in the online community is increased a thousand times more! Let's all make a New Year's resolution to work together and take our framework to the roof!

MERRY XMAS!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Thursday, December 15, 2011

Hey Mom we are on the Hanselminutes show!

At 11.00 PST on Thursday 15th December I will be appearing live on Scott Hanselman’s podcast Hanselminutes. During the course of the show Scott and I will be discussing eXpand framework and its relationship with XAF. Please tune in and show some support for your favorite frameworks! We built this thing together guys and this is an exciting event for all of us.

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Thursday, December 1, 2011

You have changes? I have Workflow!

WF4 uses a service oriented architecture and as a result any problem can be decoupled into smaller, easily solvable and testable services. XAF uses MVC architecture which, in a sense, is very similar to that used by WF4. We can compare XAF’s controllers to WF4 services. Moreover XAF’s Application does the same job as the WF4 server. The upshot of all this is that users should be able to get the feel of WF4 in no time at all. The XAF workflow module introduces a new layer that makes the already decoupled services aware of our business classes. After this the sky is the limit and over the next few posts I aim to demonstrate some of what can be achieved. For example the next post will focus on creating an event driven workflow initialization engine.

To get back to today’s post, we will discuss an implementation that is very decoupled and as a result it has very limited dependencies on other modules. It is worth noting that all XAF’s features are decoupled, persistent objects take on the role of domain mappers.

Take these requirements;

  • an end user needs to be able to input an object type (and or) a property name,
  • an object change needs to start the workflow either at client or at sever,
  • workflows need to be aware of the object that has changed, its PropertyName and its property OldValue.

The custom workflow definition

We cannot use the default XAF XpoWorkFlowDefinition class in any way. This is because there are no fields to store the PropertyName and its OldValue. We should not even derive from the default XpoWorkFlowDefinition because we may face difficulties as this class is used by our workflow server. To cope with this issue it is necessary to create a custom ObjectChangedWorkflow definition as shown.

image

While we are doing this we also need to modify the default xaml of the workflow and add the two more arguments (propertyName, oldValue) as per our requirements.

image

Below you can see the UI of this custom workflow definition,

image

Up to here XAF has made things very straightforward for us. We have designed a normal persistent class to store our data and we have used attributes (PropertyEditorType, DataStourceProperty, TypeConverter etc) to configure the UI.

Registration of custom workflow definition

The next step is to register this custom workflow definition. To help with this task, eXpand, provides the WorkflowStartService<T> where T is the type of workflow. Furthermore for ObjectChangeWorkflow definitions the implementation is rather easy since there are no further requirements.

public class ObjectChangedWorkflowStartService : WorkflowStartService<ObjectChangedWorkflow> {

public ObjectChangedWorkflowStartService()

: base(TimeSpan.FromMinutes(1)) {

}

public ObjectChangedWorkflowStartService(TimeSpan requestsDetectionPeriod) : base(requestsDetectionPeriod) { }

protected override bool NeedToStartWorkflow(IObjectSpace objectSpace, ObjectChangedWorkflow workflow) {

return true;

}

protected override void AfterWorkFlowStarted(IObjectSpace objectSpace, ObjectChangedWorkflow workflow, Guid startWorkflow) {

}

}

Start workflow - Track Object Changes

Now, when I have registered workflows on the server, it's time to return to my task: start a workflow when a property has been changed.
In XAF, I can track changes with the help of the ObjectSpace.Committing and ObjectSpace.ObjectChanged events. However because we need to create only one request per object change, it is advisable to collect the changes in an array.

public class StartWorkflowOnObjectChangeController : ViewController<ObjectView> {

protected override void OnActivated() {

base.OnActivated();

if (TypeHasWorkflows()) {

ObjectSpace.ObjectChanged += PopulateObjectChangedEventArgs;

ObjectSpace.Committing += StartWorkFlows;

}

}

void PopulateObjectChangedEventArgs(object sender, ObjectChangedEventArgs objectChangedEventArgs) {

if (!string.IsNullOrEmpty(objectChangedEventArgs.PropertyName)) {

var changedEventArgs = _objectChangedEventArgses.FirstOrDefault(args => args.Object == objectChangedEventArgs.Object && args.PropertyName == objectChangedEventArgs.PropertyName);

if (changedEventArgs != null) {

_objectChangedEventArgses.Remove(changedEventArgs);

_objectChangedEventArgses.Add(new ObjectChangedEventArgs(changedEventArgs.Object, changedEventArgs.PropertyName, changedEventArgs.OldValue, objectChangedEventArgs.NewValue));

} else

_objectChangedEventArgses.Add(objectChangedEventArgs);

}

}

void StartWorkFlow(ObjectChangedEventArgs objectChangedEventArgs, ObjectChangedWorkflow objectChangedWorkflow) {

var o = objectChangedEventArgs.Object;

ITypeInfo typeInfo = XafTypesInfo.Instance.FindTypeInfo(o.GetType());

object targetObjectKey = typeInfo.KeyMember.GetValue(o);

if (objectChangedWorkflow.ExecutionDomain == ExecutionDomain.Server) {

CreateServerRequest(objectChangedEventArgs, objectChangedWorkflow, targetObjectKey, typeInfo);

} else {

InvokeOnClient(objectChangedEventArgs, objectChangedWorkflow, targetObjectKey);

}

}

As you will have noticed we have not used the default VS naming for ObjectSpace event handlers. This is because the names that have chosen give a more specific idea of how each method works.

The ObjectChanged event occurs each time a property is changed and the changes are collected in the objectChangedEventArgses array. The Committing event occurs once changes are ready to be sent to the server and workflows start for each entry. We have introduced two options for starting and executing workflows;

  1. Execute synchronously and locally,
  2. Send a request to the server and execute at the server asynchronously

Execute a workflow synchronously on the client

The next stage is to create activities at the client then on ObjectSpace CommitChanges from appropriate WorkflowDefinition and execute them immediatelly

public class StartWorkflowOnObjectChangeController : ViewController<ObjectView> {

void InvokeOnClient(ObjectChangedEventArgs objectChangedEventArgs, ObjectChangedWorkflow objectChangedWorkflow, object targetObjectKey) {

Activity activity = ActivityXamlServices.Load(new StringReader(objectChangedWorkflow.Xaml));

var dictionary = ObjectChangedStartWorkflowService.Dictionary(targetObjectKey, objectChangedEventArgs.PropertyName, objectChangedEventArgs.OldValue);

WorkflowInvoker.Invoke(activity, dictionary);

}


This is a simple code which can be found in nearly any WF4 example at http://www.microsoft.com/download/en/details.aspx?id=21459.

Send a request to start workflow on the server

The second of our two methods involves starting the workflow at the server. Now we need to notify the server of the values of those arguments as well. In the manually starting workflows post we learnt that XAF does this by using XpoStartWorkflowRequest. This class has a different design however, and may create issues since it is used by XAF default services. Therefore instead of deriving from XpoStartWorkflowRequest we need to design a similar custom class.

public class ObjectChangedXpoStartWorkflowRequest : WFBaseObject, IObjectChangedWorkflowRequest {

[TypeConverter(typeof(StringToTypeConverter))]

public Type TargetObjectType {

get { return _targetObjectType; }

set { SetPropertyValue("TargetObjectType", ref _targetObjectType, value); }

}

#region IDCStartWorkflowRequest Members

public string TargetWorkflowUniqueId {

get { return GetPropertyValue<string>("TargetWorkflowUniqueId"); }

set { SetPropertyValue("TargetWorkflowUniqueId", value); }

}

[ValueConverter(typeof(KeyConverter))]

public object TargetObjectKey {

get { return GetPropertyValue<object>("TargetObjectKey"); }

set { SetPropertyValue<object>("TargetObjectKey", value); }

}

#endregion

#region IObjectChangedWorkflowRequest Members

public string PropertyName {

get { return _propertyName; }

set { SetPropertyValue("PropertyName", ref _propertyName, value); }

}

[ValueConverter(typeof(SerializableObjectConverter))]

[Size(SizeAttribute.Unlimited)]

public object OldValue {

get { return _oldValue; }

set { SetPropertyValue("OldValue", ref _oldValue, value); }

}

This is a very simple class, its only role is to store values in the database. Now instead of invoking workflows locally we only need to create ObjectChangedXpoStartWorkflowRequest objects.

public class StartWorkflowOnObjectChangeController : ViewController<ObjectView> {

void CreateServerRequest(ObjectChangedEventArgs objectChangedEventArgs, ObjectChangedWorkflow objectChangedWorkflow, object targetObjectKey, ITypeInfo typeInfo) {

var request = ObjectSpace.CreateObject<ObjectChangedXpoStartWorkflowRequest>();

request.TargetWorkflowUniqueId = objectChangedWorkflow.GetUniqueId();

request.TargetObjectType = typeInfo.Type;

request.TargetObjectKey = targetObjectKey;

request.PropertyName = objectChangedEventArgs.PropertyName;

request.OldValue = GetOldValue(objectChangedEventArgs);

}

In the next step we are going to create a service to consume these values from the server and start a workflow,

public class StartWorkflowOnObjectChangeService : BaseTimerService {

public override void OnTimer() {

using (var objectSpace = ObjectSpaceProvider.CreateObjectSpace()) {

//get all requests from the database

foreach (var request in objectSpace.GetObjects<ObjectChangedXpoStartWorkflowRequest>()) {

//find workflow

var definition = GetService<IWorkflowDefinitionProvider>().FindDefinition(request.TargetWorkflowUniqueId);

if (definition != null && definition.CanOpenHost) {

//Start the workflow passing in PropertyName && OldValue

if (GetService<ObjectChangedStartWorkflowService>().StartWorkflow(definition.Name,

request.TargetWorkflowUniqueId, request.TargetObjectKey, request.PropertyName, request.OldValue)) {

objectSpace.Delete(request);

objectSpace.CommitChanges();

}

}

}

}

}

At this point our server has all the information it needs to start workflows with arguments taken from persistent ObjectChangeXpoStartWorkFlowRequest objects.

I must admit that I have fully enjoyed preparing this post. The decoupled development experienced offered by the WF service oriented model is something that really appeals to me. At the same time XAF’s workflow module implementation made modeling the requirements a simple and enjoyable process. As usual it was possible to work directly on the problem and leave the hard work to non XAF developers.

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Friday, November 18, 2011

November news

The last few months have been exciting here at eXpand. First and foremost the number of you that are contributing to the project has shot up which is great news for all of us! Also (as some of you may have noticed) we have earned ourselves a place in the DevExpress start menu. There has been a flurry of activity on the development side too, as a result we are pleased to offer a host of innovative new features. Finally eXpand is now registered at http://nuget.org/ so it can be accessed via Visual Studio.

Contributors!

First of all we must all say a big thank you to everybody that has been contributing to eXpand in any way. We appreciate your continuing love and support whether in the form of forum posts, code submissions, user suggestions or simply spreading the good word about eXpand! Our community is very healthy right now, I am proud of the fact that eXpand users are collaborating more than ever and sharing their ideas and stories.

Of course at the same time we must thank DevExpress for their steady support and the trust that they have placed in eXpand. By allowing eXpand a place in their start menu it’s not even easier for us to share our framework with other XAF developers.

To all those who are ready to take the leap and start contributing to our framework I can promise that being part of our community means a full share in all of the benefits. Of course the more we all contribute the more benefits there will be!

Latest Collaborations

I could talk about new features here, however I have deliberately chosen the word ‘collaborations’ in recognition of the fact that these developments are a direct result of your contributions to eXpand. It is clear that to list all of our collaborations here would take too long, but lets take a look at some of the most successful so far;

  • Masterdetail

    Offers support for actions state synchronization between master detail frames.
  • LayoutViewGridListEditor

    Enables LayoutView mode in the GridControl in List Views as in E1486.
  • Setup version

    In order to make distinguishing between versions easier we have used a version dedicated name for our setup.
  • Wizard

    Offers support for domain components.
  • Caching support

    Enables caching at client and server by using a setting in the application config file.
  • Toolbox

    When compiling our sources all eXpand assemblies will be auto registered to VS toolbox.

What's next?

More modules that will prove the simplicity and power of XAF from dedicated coders. In addition we are redesigning our site in order to establish better collaboration with all of you. We see this as an opportunity through XAF modularized architecture to share our experiences in order to give our business more relaxed weekends. We believe that with your help our job will become much easier, especially since DX is also here to guide us!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Wednesday, September 21, 2011

Creating a State Machine module for eXpand Framework–Part 2

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!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Tuesday, September 20, 2011

Calculated members creation—Pros and Cons

As promised in the previous post I will now attempt to provide a concise yet comprehensive look at the 5 possibilities that Xpand gives us when we want to create a calculated member. I will also provide discussion of the relative advantages and disadvantages of each approach. We can see all of them in action in Xpand FeatureCenter application. Remember it is only thanks to the fact that XAF/XPO have such a strong and flexible architecture that this is possible! We must also acknowledge that these approaches have evolved as a direct result of contributions from the XAF team and community.

image_thumb11

1. The XPO way –>Using Code

Take a look at the CreateCalculabeMember extension method in the code below,

public class CreateRuntimeCalculatedFieldController : ViewController {

    public override void CustomizeTypesInfo(DevExpress.ExpressApp.DC.ITypesInfo typesInfo) {

        base.CustomizeTypesInfo(typesInfo);

        XPClassInfo classInfo = XafTypesInfo.XpoTypeInfoSource.XPDictionary.GetClassInfo(typeof(Customer));

        if (classInfo.FindMember("SumOfOrderTotals")==null) {

            var attributes = new Attribute[] {new PersistentAliasAttribute("Orders.Sum(Total)")};

            XPCustomMemberInfo calculabeMember = classInfo.CreateCalculabeMember("SumOfOrderTotals", typeof(float), attributes);

            typesInfo.RefreshInfo(typeof(Customer));

        }

    }

}

 

XPO has a dictionary of domain metadata. The metadata of each persistent object is stored in XPClassInfo classes and the metadata of their properties is stored in XPMemberInfo classes. One of the main advantages of this approach is that it  adds a new member to the XPO dictionary and therefore follows XAF to the letter. This is because in order to form the model XAF first queries the XPO dictionary and finally in order to configure the views it queries the model. Since we have worked in the data layer our calculated values will be sent to any data bound enabled control for rendering. The other benefit is that if we code there are no restrictions on what we can do, for example we can create members calling a WCF service. The sky’s the limit!

On the other hand each time we write new code we need to spend time testing and distributing it. Furthermore in certain scenarios problems can occur due to the fact that we add a new member to the object. For each new member we add to an object we are forced to deal with long properties lists in our model which can be somewhat unwieldy – imagine an  object with 200 properties.

Note; XAF is smart enough to manage all of this without even breaking a sweat. This means that its performance does not suffer in any way. It is simply that this approach can leave the user with a bit of a headache!

2. Using the model

We have extended our model in order to describe all types of calculated and runtime properties.

 

image_thumb14_thumb

 

Having done this makes it pretty easy to utilize the powerful XPO and add members in its dictionary as with the previous approach. This means this method shares some of the benefits we mentioned above. However as we are not writing code we can’t enjoy the same level of flexibility unless we utilize ModelUpdaters. Also as the columns can only be created using model editor they are only useful in situations when we have permission to modify the model. Finally we could end up with a huge number of properties here too for the same reason we identified previously.

 

All this is not to say that this approach doesn’t have its own unique advantages. For example the model difference can be stored in a database or xml file making it easy to distribute. To this end we can use the build in API or a specialized module like IO. Moreover it is possible to develop on site (client). Of course working at the model level allows us to use a model manager module like ModelDifference which is a great aid. It can be used to distribute the unbound columns to users, roles and even to other applications. In addition since ModelDifference supports both platforms it is possible to create calculated members without restarting the application or IIS.

3.WorldCreator extended members

This module maps the structure of the XPMemberInfo class to a persistent object. This approach is again similar to model approach (and once again has similar benefits) however there is a crucial difference. Instead of using the model to gather data to form the calculated members we use the input taken from the UI.

image_thumb17_thumbimage_thumb20_thumb

Using the UI is simplicity itself! It requires no technical knowledge whatsoever as the user is only required to choose from a set of basic options. We can still code if we wish, this time by creating persistent objects and then leaving eXpand to take care of the rest. That being said the real beauty of this approach is that the product can be developed on site without the user having to write a single line of code or using a sophisticated tool like Model Editor. Moreover we can enjoy ease of distribution due to the fact that our objects are stored in the database. As the metadata is now in the form of persistent objects locating it and working with it is as easy as ever.

By now it should be clear that as we are still relying on XPO we are faced with the same old problem regarding multiple views.

4. Using a WordCreator Dynamic Assembly

Our fourth approach uses the same module to create dynamic persistent assemblies using code generation templates. XAF is the best framework to describe domains which is evidenced by the way that  WorldCreator maps the Assembly structure to persistent objects and auto generates a flexible UI.

image_thumb23_thumb

Using the templates will create a dynamic assembly with exactly the same code and structure as if we had taken the time to design it ourselves inside VS. Distribution is still easy since is everything is in the database. The fact that we script at runtime means that our options are almost unlimited when taking this approach.

5. The Unbound Column

Extending the model with an Unbound column node as shown makes it possible to create Unbound grid columns and set their unbound expressions.

image_thumb2

 

image_thumb5

This is the only approach that allows us to work directly on views without utilizing XPO. This means that we gain the maximum level of flexibility since it is possible to have different columns for the same object views. Therefore we can work on this column independently. At the same time the end user can change the UnboundExpression at runtime using expression editor (windows only). When it comes to distribution we find the same advantages as we do whenever we deal with the model.

As with the second approach we lose some flexibility because we are not writing code but again we can get round this using ModelUpdaters. In addition our columns still cannot be used when we do not have permission to modify the model. Another disadvantage concerns the fact that XAF is designed to make all calculations in the data layer and send the values to controls by applying an MVC pattern. A few years after XAF was released, Microsoft built Silverlight featuring very similar architecture. Taking into account the various factors this has been recognized as the optimal approach. Although using Unbound columns allows us to work in a different way we need to write extra code to support each control (Tree, Pivot etc) because the calculations are performed in the UI.


We are happy to read your feedback about this!. Remember that your questions are the best candidates for future posts.

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

Friday, September 16, 2011

eXpandFrameWork Supporting Unbound Columns

Recently in Xpand forums Dionisis Soldatos raised a question about how unbound columns can be implemented with XAF. Unbound columns along with their UnboundExpression can be used for creating calculated fields even at runtime. Since we are talking about unbound grid columns it should be obvious that we will operate at the UI level by modifying the grid control columns. However lets do a deep dive inside XAF model to extend it as needed!

The Model

By now we are all used to XAF providing us with excellent out of the box solutions which negate the need for us to write hundredths of lines of code. This of course means money saved during developing and ultimately your product hits the market faster. Why spend time reinventing the wheel when the XAF team have already done the hard work for you?

XAF creates the model by reading the metadata of our classes, this model has 3 types of view. One of these is the ListView which can be displayed with data source enabled controls like Grid controls. ListView has columns which correspond to existing object properties metadata and when XAF creates a Grid at runtime it queries model’s ListView columns. It then creates and configures Grid columns from their attributes. These stages are well tested and it is preferable to use them in our solution and override the unnecessary stages. For example we could create a normal model column node using XAF default Add/Column menu. After the Grid column is created it we simply need a few lines of code to make it unbound and set its Unbound Expression.

image

In order to store this expression we still need to extend model’s ListView with an attribute. The model can be extended either by registering an interface at ModuleBase.ExtendModelInterfaces or by deriving it from an already registered interface. I am going to take the latter options by deriving from IModelColumn interface which I will explain as we go.

public interface IModelColumnUnbound : IModelColumn {

[Category("eXpand")]

bool ShowUnboundExpressionMenu { get; set; }

[Category("eXpand")]

[Required]

string UnboundExpression { get; set; }

}

XAF model editor is a highly sophisticated tool which has the capability to recognize that we extended the model. It then takes care of the vital step of adding an entry to the Add menu for creating Unbound columns.

image

Now it is possible to create a new type of column with 2 extra attributes as shown,

image

Moving on we need to set the mandatory PropertyName attribute shown above to an always existing object property name. Remember XAF requires this in order to behave as designed. To this end we are going to set as PropertyName the object’s key property name using this simple DomainLogic class,

[DomainLogic(typeof(IModelColumnUnbound))]

public class IModelColumnUnboundLogic {

public static string Get_PropertyName(IModelColumnUnbound columnUnbound) {

return ((IModelListView)columnUnbound.Parent.Parent).ModelClass.KeyProperty;

}

As a result (PropertyName, PropertyEditorType and Caption) attributes will be populated the next time we create a ColumnUnbound Node. However these will be fixed values and it is preferable to hide them from the end user. At the same time we need to mark Caption attribute as required and remove its default value. To do all of this we just need to extend our IModelColumnUnbound interface like this,

image

Note; Although PropertyName and Caption belong to IModelColumn using the new operator it is possible to override them!

We have now finished with the model modifications and for our ColumnUnbound nodes XAF by design will create a new column pointing back to object’s key property metadata.

The UI

A key benefit of XAF’s commitment to design patterns, specifically to the Single responsibility principle, is that it provides us with the model’s synchronizer classes. These can be used to synchronize our model with the control and vice versa. It is only necessary to derive from the abstract ModelSyncroniser<T,V> and implement ApplyModeCore method to synchronize the control and from SynchronizeModel to do the same with the model.

public class UnboundColumnSynchronizer: ModelSynchronizer<GridListEditor, IModelListView> {

public UnboundColumnSynchronizer(GridListEditor control, IModelListView model)

: base(control, model) {

}

protected override void ApplyModelCore() {

var xafGridColumns = GetXafGridColumns();

foreach (var column in xafGridColumns) {

var modelColumnUnbound = (IModelColumnUnbound)column.Model;

column.FieldName = modelColumnUnbound.Id;

column.UnboundType = UnboundColumnType.Object;

column.OptionsColumn.AllowEdit = false;

column.ShowUnboundExpressionMenu = modelColumnUnbound.ShowUnboundExpressionMenu;

column.UnboundExpression = modelColumnUnbound.UnboundExpression;

}

}

IEnumerable<XafGridColumn> GetXafGridColumns() {

IEnumerable<XafGridColumn> xafGridColumns =

Model.Columns.OfType<IModelColumnUnbound>().Select(

unbound => Control.GridView.Columns[unbound.PropertyName] as XafGridColumn).Where(column => column != null);

return xafGridColumns;

}

public override void SynchronizeModel() {

var xafGridColumns = GetXafGridColumns();

foreach (var xafGridColumn in xafGridColumns) {

((IModelColumnUnbound) xafGridColumn.Model).UnboundExpression = xafGridColumn.UnboundExpression;

}

}

}

The above code uses the GetXafGridColumns method to return the grid columns that correspond to IModelColumnUnbound nodes. The web implementation is very similar and can be found here.

All that is left is to register our UnboundColumnSynchronizer like this,

public class UnboundColumnController : ViewController<ListView> {

protected override void OnActivated() {

base.OnActivated();

var gridListEditor = View.Editor as GridListEditor;

if (gridListEditor != null)

gridListEditor.CreateCustomModelSynchronizer += GridListEditorOnCreateCustomModelSynchronizer;

}

void GridListEditorOnCreateCustomModelSynchronizer(object sender, CreateCustomModelSynchronizerEventArgs createCustomModelSynchronizerEventArgs) {

createCustomModelSynchronizerEventArgs.ModelSynchronizer = new UnboundColumnSynchronizer((GridListEditor)sender, View.Model);

}

}

Note; Setting ShowUnboundExpressionMenu to true is only supported by Windows platform. There, an end user can modify the UnBoundExpression by invoking Grid’s expression editor

Together with the unbound column Xpand allows for up to 5 different approaches to creating calculated fields. In the next post we will discuss the pros and cons of each approach so stay tuned!

Subscribe to XAF feed
Subscribe to community feed

DiggIt!