in
Forums
Blogs
DevExpress.com
Client Center
Support Center
DevExpress Channel

Gary's Blog

  • XAF 10.1 Sneak Peak

    One of the major features that you will see as of 10.1 is the Typed Application Model.

    As you will know XAF allows developers to define business class and then builds the Application Model from these declarations and generates a UI based on them. Thereafter if you want to change the UI you simply customise the Application Model using the Model Editor. The Application Model is therefore a cornerstone of XAF and we have re-designed it from the bottom up in 10.1.

    Firstly, let’s take a look at what the Application Model is like now, prior to 10.1. It is currently defined by it’s schema and looks something like this:

    <Element Name="Application">
      <Element Name="Views">
        <Element Name="ListView">
          <Element Name="Variants" >
            <Attribute Name="Current" />
            <Element Name="Variant" >
              <Attribute Name="ID" />
              <Attribute Name="Caption" />
              <Attribute Name="ViewID" />
            </Element>
          </Element>
        </Element>
      </Element>
    </Element>

    This snipped declares the Variants node, as a child node of the ListView node. As you can see, in this simplified example, the only thing that attributes haveare names. There are no types declared. So, even if the ID attribute is intended to hold an integer and Caption to hold a string, there is really no difference. The untyped Application Model stores its data as a set of string. For a caption this is fine, for an integer though, it’ll first need to be converted to a string representation. When retrieving its value, it’ll need to be converted again from a string to an integer. What’s more, you need to remember that an integer is stored there, because all the Application Model stores are plain strings. What a PITA!

    Now let’s take a look at how the Application Model worked under the hood. This’ll be a pretty simplified look but it’ll do for our purposes. When an application is started, the types info subsystem (see HowTo: Access Business Class Metadata) collects metadata on all the business classes declared in the application. After this, the Application Model generation is started. Firstly, the BOModel node’s child nodes (see BOModel Node) are generated and filled with values from the types info subsystem. Then all the other Application Model’s nodes and their attributes are filled with data.

    After this initial generation is complete, the Application Model is filled with custom data from all the Modules used in the application.

    At this point we have the initial Application Model, that part which can’t be changed by the user. Then the Application Model is filled with each user’s customizations . Now, the Application Model generation is complete, and we can use our application.

    Now that we know how our untyped Application Model works, we can see the disadvantages:

    1. We COMPLETELY generate ALL THE Application Model at application startup. This is slow.
    2. Memory consumption is far from being optimal. Each attribute contains a string value, not a reference. Usually, there are a lot of duplicate info. For example, a string holding the name of a business class can be found in: types info subsystem, a BOModel node’s attribute, an attribute of each of the Views declared for the class (usually, there are several Views for each business type, at least two or three) and so on.
    3. The Application Model is untyped, and working with it isn’t straightforward. Though we can use node wrappers, the process is generally bulky.

    So, in general, the current Application Model, is suboptimal. :-)

    But it is improved in 10.1 and here’s how. What we’ve done is to do away with the schema and we now use interfaces instead. So the example above would now be declared like so:

    public interface IModelViewVariants : IModelNode
    {
        IModelVariants Variants { get; set; }
    }
    
    public interface IModelVariants : IModelNode, 
        IModelList<IModelVariant>
    {
        IModelVariant Current { get; set; }
    }
    public interface IModelVariant : IModelNode
    {
        int Id { get; set; }
        string ViewID { get; set; }
        string Caption { get; set; }
    }

    Now as you see, attributes not only have names, but they also have types. Also, there’s no need to use node wrappers or convert attribute values to and from string representations. That is much better, don’t you think?

    The basic Application Model structure is described by the base IModelApplication interface. This interface defines the root Application node (its attributes and child nodes). Each Module can also extend the Application Model. This is done by specifying additional interfaces derived from the IModelNode interface.

    So, firstly, when an application is started, we collect all the interfaces that define the Application Model. Then we compile an object implementing all these interfaces. This object represents the Application Model. At this point the Application Model doesn’t hold any actual data.

    Secondly, we fill the Application Model with layers. Our new typed Application Model consists of unmerged layers. Each layer represents a separate data set. Each Module is represented by its own data layer. So, for each Module we create a separate layer and fill it with data supplied by the Module. We also create the user customizations layer and load the user customizations into it. Note that the base Application Model layer defined by the IModelApplication interface doesn’t contain any data at this point.

    At this point, the Application Model is ready to be used. When, for example, the Logon Detail View needs to be invoked, the Application Model is accessed, to retrieve the Detail View’s layout. The following code snippet illustrates this:

    IModelView modelView = 
                    Application.ModelApplication.Views[
                    “Logon_DetailView”];

    Since the Views node hasn’t been generated yet, a Views generator is invoked which in turn invokes the BOModel generator. As a result, the Views and BOModel nodes’ child nodes are created. Note however, that the created child nodes are NOT filled with data. Then, since we asked for the “Logon_DetailView” data, this node gets populated with data on the base layer. After this, XAF displays the specified View using the data.

    As you can see, the base layer of the Application Model is populated with data on-demand. In other words, if you launch an XAF application, and use only a Contact List View then only the Contact_ListView node will be populated with data. All the other View nodes of the Application Model will remain uninitialized.

    A note on ASP.NET Web XAF applications. Now, if users haven’t customized the Application Model, a single Application Model instance is shared between all the users. Moreover, even if users have customized the Application Model, the common part of it is still represented by a single instance which is also shared between the users.

    Now that we know how our typed Application Model works, we can outline its advantages:

    1. We never completely generate all the Application Model. It’s created on demand (kind of a lazy initialization). This is fast (or at least we hope it will be).
    2. Memory consumption is much more optimal. Each attribute holds a reference, not another value copy. So, for example, ideally, a string holding the name of a business class could only be found in the types info subsystem. In ASP.NET Web XAF applications, most of the Application Model (and sometimes ALL the Application Model) is shared between users.
    3. The Application Model is typed, and working with it becomes more straightforward. No more need for string conversions and use of additional artificial wrappers.

    Okay, so let’s finish up with some before and after examples:

    Defining a key:

    Before:

    <Element Name="Variant" KeyAttribute="ID" />
    After:
    [KeyProperty("Id")]
    public interface IModelEditorStateRule : IEditorStateRule {
    string Id { get; set; }
    }

    Declaring multiple child nodes:

    Before:

    <Element Name="ListView">
      <Element Name="Variants">
        <Element Name="Variant" Multiple="True" />

    After:

    public interface IModelViewVariants : IModelNode {
    IModelVariants Variants { get; set; }
    }
    public interface IModelVariants : 
        IModelNode, IModelList<IModelVariant> {
    }
    public interface IModelVariant : IModelNode { .. }

    Declaring a localisable attribute:

    Before:

    <Element Name="Variant">
      <Attribute Name="Caption" IsLocalized="True" />

    After:

    public interface IModelVariant : IModelNode {
    [Localizable(true)]
    string Caption { get; set; }
    }

    Specifying an image for an node:

    Before:

    <Element Name="Variant" ImageName="ModelEditor_ListView">

    After:

    [ImageName("ModelEditor_ListView")] 
    public interface IModelVariant : IModelNode {...

    Specifying the display property:

    Before:

    <Element Name="Variant" DisplayAttribute="Caption" />

    After:

    [DisplayProperty("Caption")]
    public interface IModelVariant : IModelNode {...

    Specifying a required attribute:

    Before:

    <Element Name="Variant" >
      <Attribute Name="ViewID" Required="True" />
    

    After:

    public interface IModelVariant : IModelNode {
    [Required()]
    string ViewID { get; set; }

    Specifying a child node’s Index attribute used to order child nodes:

    Before:

    <Element Name="Variant" >
      <Attribute Name="Index" IsNewNode="True"/>

    After:

    Nothing to do here as the base IModelNode interface already declares such an attribute.

    Using an enumeration to specify the possible values for an attribute displayed in a dropdown:

    Before:

    <Element Name="NavigationItems" >
      <Attribute Name=""
                 DefaultChildItemsDisplayStyle="""" 
                 Choice=""List,LargeIcons=""""/>

    After:

    public interface IModelNavigationItems : IModelNode {
    ItemsDisplayStyle DefaultChildItemsDisplayStyle { get; set; }

    Getting data from the Application Model. Retrieving a node:

    Before:

    DictionaryNode viewsNode = 
        Application.Model.RootNode.GetChildNode("Views");
    DictionaryNode viewNode = 
        viewsNode.FindChildNode(
        BaseViewInfoNodeWrapper.IdAttribute, viewId);

    After:

    IModelView modelView =
        Application.ModelApplication.Views[viewId];

    Getting data from the Application Model. Retrieving an attribute value:

    Before:

    DictionaryNode viewNode = 
        viewsNode.FindChildNode(
        BaseViewInfoNodeWrapper.IdAttribute, viewId);
    
    string myCustomFilter = 
        viewNode.GetAttributeValue("MyCustomFilter");

    After:

    IModelViewMyExtention myModelView = 
        (IModelViewVariants)
            Application.ModelApplication.Views[viewId];
    
    string myCustomAttrValue = myModelView.MyCustomFilter;

    Well that’s all for this post, until next time – happy XAF-ing! :-)
  • Santa Gets it Done With XPO

    After my last post I said we’d take a look at one to many associations next time, and that’s what we are doing to do now. With it coming up to Christmas I thought we would write a little application to give Santa a hand with his deliveries – I mean the poor old guy’s got a lot of work to do so I figure he could use all the help he can get, right?

    So we are going to write an application that will provide him with delivery instructions, detailing which presents to deliver to which house. To do that, the first thing we need is a class to define a house:

    using System;
    using DevExpress.Xpo;
    
    namespace XPOChristmas
    {
        public class House : XPObject
        {
            public House(Session session)
                : base(session)
            { }
    
            private string address;
            public string Address
            {
                get
                {
                    return address;
                }
                set
                {
                    SetPropertyValue("Address", ref address, value);
                }
            }
    
            private Country country;
            public Country Country
            {
                get
                {
                    return country;
                }
                set
                {
                    SetPropertyValue("Country", ref country, value);
                }
            }        
    
            [Association("House-Presents")]
            public XPCollection<Present> Presents
            {
                get
                {
                    return GetCollection<Present>("Presents");
                }
            }
    
            public void ShowDeliveryInstructions()
            {
                Console.WriteLine(String.Format("Santa, deliver to {0} the following: ", Address));
                foreach (Present p in Presents)
                {
                    Console.WriteLine(String.Format("   {0}", p.Description));
                }
                Console.WriteLine();
            }        
        }
    }

    As you can see, this class extends XPObject just like you’ve seen in all the XPO example so far. It also has a property detailing the address of the house. This is a simple string property that we’ve seen many times before, so we need not say any more about that.

    We need not say too much about the country property either, it simply holds an enum describing the country in which the house is located, the definition of the enum is very simple:

    using System;
    
    namespace XPOChristmas
    {
        public enum Country
        {
            Scotland,
            England,
            Ireland,
            Wales
        }
    }

    The next property however, is a little special. This property holds the child relationship with all the presents that are due to be delivered to this house. The first thing to notice about the property is that it is decorated with the Association attribute. This attribute must be unique in the database and the string name must match on both the parent and child ends of the association. Note the use of the helper function GetCollection<T> which returns all the children in the association.

    The last thing to notice about this class is the ShowDeliveryInstructions() method that displays instructions for the house and then walks the graph of children and displays instructions for them.

    The next thing we need then is a class to describe the presents:

    using System;
    using DevExpress.Xpo;
    
    namespace XPOChristmas
    {
        public class Present : XPObject
        {
            public Present(Session session)
                : base(session)
            { }
    
            private string description;
            public string Description
            {
                get
                {
                    return description;
                }
                set
                {
                    SetPropertyValue("Description", ref description, value);
                }
            }
    
            private House house;
            [Association("House-Presents")]
            public House House
            {
                get
                {
                    return house;
                }
                set
                {
                    SetPropertyValue("House", ref house, value);
                }
            }
    
        }
    }

    This class has a Description property which we need not say too much about and also a property which points to it’s parent house. Note the association attribute decorating the property and pay particular attention to the fact that the name is the same name that is on the parent end of the association.

    Once we have those two classes we just need code to drive it all:

    using System;
    using System.Linq;
    using DevExpress.Xpo;
    
    namespace XPOChristmas
    {
        class Program
        {
            static void Main(string[] args)
            {
    
                //GS - Persist some houses and presents for Santa
                using (UnitOfWork uow = new UnitOfWork())
                {
                    House house = new House(uow)
                    {
                        Address = "27 Cromarty Road",
                        Country = Country.Scotland
                    };
    
                    new Present(uow)
                    {
                        Description = "Little Blue Car",
                        House = house
                    };
    
                    new Present(uow)
                    {
                        Description = "Big Book For Boys",
                        House = house
                    };
    
                    House nextHouse = new House(uow)
                    {
                        Address = "27 Chelsea Lane",
                        Country = Country.England
                    };
    
                    new Present(uow)
                    {
                        Description = "Little Pink Car",
                        House = nextHouse
                    };
    
                    new Present(uow)
                    {
                        Description = "Big Book For Girls",
                        House = nextHouse
                    };
    
                    uow.CommitChanges();
                }
    
                //GS - Get delivery instructions for all houses
                Console.WriteLine("Displaying all houses...");
                Console.WriteLine();
                using (UnitOfWork uow = new UnitOfWork())
                {
                    XPQuery<House> houseQuery = new XPQuery<House>(uow);
    
                    foreach (var house in houseQuery)
                    {
                        house.ShowDeliveryInstructions();
                    }
                }
    
                //GS - Get delivery instructions for the house in Scotland
                Console.WriteLine("Displaying Scottish houses...");
                Console.WriteLine();
                using (UnitOfWork uow = new UnitOfWork())
                {
                    XPQuery<House> houseQuery = new XPQuery<House>(uow);
    
                    var houses = from h in houseQuery 
                                where h.Country == Country.Scotland 
                                select h;
    
                    foreach(var house in houses )
                    {
                        house.ShowDeliveryInstructions();
                    }
                }
    
            }
        }
    }

    In this code we persist some house and present objects and then retrieve them from the database before asking them to display their delivery instructions. There is nothing really special to say about this code, we have seen many such examples. The only thing to note is that the one to many associations can be specified from either end. In this case I specify it from the child side, because I found that most convenient, but either will do.

    Now that I have shown you the code, all that remains is to run it:

    image

    And that is it really, with such good help how could Santa fail to get all his presents delivered on time? That brings this post to a close, so until next time, happy XPOing! :-)

  • XPO Rocks Your World With Earthquakes

    In the last post I made, looking at analysing weather information using XPO, one of the commenters asked if we could see more examples using different types of associations between objects, so I thought I’d start off by posting something on one to one associations. To do this we are going to look at earthquakes (well why not?).

    Now the USGS are good enough to provide us with a feed of earthquakes in, pretty much, real time. Since the bigger the better, we’ll look at quakes magnitude 5 and above happening in the previous 7 days. The USGS publishes an atom feed of these at: http://earthquake.usgs.gov/earthquakes/catalogs/7day-M5.xml.

    To use this feed, the first thing we are going to do is to define an Earthquake entity and a GeoPoint entity, these entities will have a one to one relationship:

    using System;
    using DevExpress.Xpo;
    
    namespace EarthquakeFetcher.Model
    {
        public class EarthQuake: XPObject
        {
            public EarthQuake(Session session)
                : base(session)
            { }
    
            private string title;
            public string Title
            {
                get
                {
                    return title;
                }
                set
                {
                    SetPropertyValue("Title", ref title, value);
                }
            }
    
            private DateTime timeStamp;
            public DateTime TimeStamp
            {
                get
                {
                    return timeStamp;
                }
                set
                {
                    SetPropertyValue("TimeStamp", ref timeStamp, value);
                }
            }
    
            private GeoPoint geoPoint;
            public GeoPoint GeoPoint
            {
                get
                {
                    return geoPoint;
                }
                set
                {
                    if (geoPoint == value)
                        return;
    
                    //GS - Store a reference to the former geoPoint.
                    GeoPoint previousGP = geoPoint;
                    geoPoint = value;
    
                    if (IsLoading) return;
    
                    //GS - Remove the previous reference if there is one.
                    if (previousGP != null && previousGP.EarthQuake == this)
                        previousGP.EarthQuake = null;
    
                    //GS - Attach this reference
                    if (geoPoint != null)
                        geoPoint.EarthQuake = this;
    
                    //GS - Signal the change
                    OnChanged("GeoPoint");
                }
            }
    
            //GS - Override ToString() to give us a pretty label
            public override string ToString()
            {
                return Title;
            }
    
            
        }
    }
    using System;
    using DevExpress.Xpo;
    
    namespace EarthquakeFetcher.Model
    {
        public class GeoPoint : XPObject
        {
            public GeoPoint(Session session)
                : base(session)
            { }
    
            private string point;
            public string Point
            {
                get
                {
                    return point;
                }
                set
                {
                    SetPropertyValue("Point", ref point, value);
                }
            }
    
            private int elevation;
            public int Elevation
            {
                get
                {
                    return elevation;
                }
                set
                {
                    SetPropertyValue("Elevation", ref elevation, value);
                }
            }
    
            private EarthQuake earthQuake;
            public EarthQuake EarthQuake
            {
                get
                {
                    return earthQuake;
                }
                set
                {
                    if (earthQuake == value)
                        return;
    
                    //GS - Store a reference to the former EarthQuake.
                    EarthQuake previousQuake = earthQuake;
                    earthQuake = value;
    
                    if (IsLoading) return;
    
                    //GS - Remove the previous reference if there is one.
                    if (previousQuake != null && previousQuake.GeoPoint == this)
                        previousQuake.GeoPoint = null;
    
                    //GS - Attach this reference
                    if (earthQuake != null)
                        earthQuake.GeoPoint = this;
    
                    //GS - Signal the change
                    OnChanged("EarthQuake");
                }
            }
        }
    }

    Note the syntax on the one to one associations. Now then, the next thing we have to do is to read the feed, create objects from it and persist them:

    using System;
    using System.Xml.Linq;
    using DevExpress.Xpo;
    using EarthquakeFetcher.Model;
    
    namespace EarthquakeFetcher
    {
        class Program
        {
            static void Main()
            {                      
                //GS - Fetch the feed
                XDocument quakeFeed = 
                    XDocument.Load(@"http://earthquake.usgs.gov/earthquakes/catalogs/7day-M5.xml");
    
                //GS - Add the required namespaces
                XNamespace nsAtom = "http://www.w3.org/2005/Atom";
                XNamespace nsGeoRSS = "http://www.georss.org/georss";
    
                //GS - Persist the quake information
                using (UnitOfWork uow = new UnitOfWork())
                {
                    foreach (var quake in quakeFeed.Descendants(nsAtom + "entry"))
                    {
                        new EarthQuake(uow)
                         {
                             Title = quake.Element(nsAtom + "title").Value,
                             TimeStamp = DateTime.Parse(quake.Element(nsAtom + "updated").Value),
                             GeoPoint = new GeoPoint(uow)
                             {
                                 Point = quake.Element(nsGeoRSS + "point").Value,
                                 Elevation = Convert.ToInt32(quake.Element(nsGeoRSS + "elev").Value)                             
                             }
                         };
                    }
                    uow.CommitChanges();                
                }
            }
        }
    }

    There isn’t anything in the above code worth commenting on I think, just note the addition of the namespaces for atom and GeoRSS. Running this code will cause the present list of quakes to be persisted. Note, this code is for demonstration and instructional purposes only and has had “guard” code removed for clarity. If you were to put this code into production you would have to handle the case of duplicate quake entries being persisted if you were to run this code more than once in any 7 day period.

    Now that we have our persisted entities, it’s time to use them. First, we’ll create a view to display them:

    image

    The code behind this form looks like this:

    using System;
    using System.Windows.Forms;
    using EarthquakeFetcher.Model;
    using System.Diagnostics;
    
    namespace EarthquakeViewer
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                FillListBox();
                SelectFirstItemInListBox();
            }
    
            private void FillListBox()
            {
                QuakeListBox.Items.AddRange(EarthquakeHelper.GetAllEarthquakes.ToArray());            
            }
    
            private void SelectFirstItemInListBox()
            {
                if(QuakeListBox.Items.Count > 0)
                    QuakeListBox.SelectedIndex = 0;
            }
    
            private void ShowOnMapButton_Click(object sender, EventArgs e)
            {
                EarthQuake quake = QuakeListBox.SelectedItem as EarthQuake;
                ShowQuakeOnMap(quake);
            }
    
            private static void ShowQuakeOnMap(EarthQuake quake)
            {
                if (!String.IsNullOrEmpty(quake.GeoPoint.Point))
                    Process.Start(String.Format(@"http://maps.google.com/?q={0}", quake.GeoPoint.Point));
            }
    
            private void MapMaxElevationButton_Click(object sender, EventArgs e)
            {
                ShowQuakeOnMap(EarthquakeHelper.GetHighestElevatedQuake);
            }
    
            private void MapMinElevationButton_Click(object sender, EventArgs e)
            {
                ShowQuakeOnMap(EarthquakeHelper.GetLowestElevatedQuake);
            }
        }
    }

    So, from the code we can see that we have a helper class, EarthquakeHelper, that gives us a few helper methods, let’s take a look at those now:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using DevExpress.Xpo;
    using EarthquakeFetcher.Model;
    
    namespace EarthquakeViewer
    {
        public class EarthquakeHelper
        {
            //GS - Return all quakes. This is safe as there will not be too many in a 7 day period.
            public static List<EarthQuake> GetAllEarthquakes
            {
                get
                {
                    XPQuery<EarthQuake> quakeQuery = new XPQuery<EarthQuake>(XpoDefault.Session);
                    return (from q in quakeQuery select q).ToList<EarthQuake>();
                }
            }
    
            public static EarthQuake GetHighestElevatedQuake
            {
                get
                {
                    XPQuery<EarthQuake> quakeQuery = new XPQuery<EarthQuake>(XpoDefault.Session);
                    return (from q in quakeQuery
                        orderby q.GeoPoint.Elevation descending
                        select q).First<EarthQuake>();
                }
            }
    
            public static EarthQuake GetLowestElevatedQuake
            {
                get
                {
                    XPQuery<EarthQuake> quakeQuery = new XPQuery<EarthQuake>(XpoDefault.Session);
                    return (from q in quakeQuery
                            orderby q.GeoPoint.Elevation ascending
                            select q).First<EarthQuake>();
                }
            }
        }
    }

    You can see that the GetAllEarthquakes property does what is says on the tin, it returns all of the quakes in the database. Again all “guard” has been removed for clarity and we are assuming that the database only contains the last 7 days worth of data. The other two properties use linq to XPO to return the highest and lowest earthquakes by elevation. These three properties return quakes that are then used by the three buttons on the form to show the location, via Google maps, of the required earthquakes:

    image

    This post is not only interesting from an XPO point of view, but also from an earthquake point of view. I mean, who of you knew there were so many earthquakes happening right now? Anyway, that’s it for this post, in the next one we’ll take a closer look at at one to many and many to many associations. Until then, happy XPOing!

  • That Was TechEd that Was!

    And what a busy week it was! with 7,300 attendees at the Messe in Berlin the exhibition hall was busy from the word go. Of course, it may have helped that Microsoft deliberately planned the conference to coincide with the 20th anniversary of the fall of the Berlin wall, but whatever it was TechEd 09 was a sell out event and that is a rare occurrence at the moment.

    The partner event on the Monday evening was busy, but then we expected it to be and we gave away most of our T shirts, in fact Rachel had to order reinforcements for the rest of the week! What we didn’t expect of course was that the rest of the week would be just as busy. There were literally hordes (that is the collective noun for developers, right?) of developers around the booth, eager to hear the latest news about their favourite product. In the past the main topic of interest as been WPF and Silverlight, those two platforms vying for supremacy amongst developer’s interest. However, the last two conferences I’ve been too, and again here, developers have been showing more and more interest in XAF. Of course, as the evangelist for XAF that is music to my ears. In fact at this conference the very first person I spoke to wanted a demonstration of XAF! So, go us!

    On Thursday I delivered a session to the architecture track on Technical Debt. It fits in quite well with the development issues that RefactorPro! can help you with and also, it deals with the interest on Technical Debt – something CodeRush can help you reduce. So, whilst the talk doesn’t directly promote our tools, my presentation speaks to the philosophy behind them. I’d never spoken to a TechEd audience before and I must admit I thoroughly enjoyed the experience. The atmosphere is quite different from other conference and user groups that I’ve spoken at. Very intense, and let’s just say, you’d better be on top of your game or the audience will call you out. :-)

    One of the highlights of any large conference is meeting old friends, both customers and other “techies”, that I only get to meet two or three times a year at the big conferences. One of them (thanks Betsy) took, what Rachel reliably informs me is, the only half decent photograph of me at TechED. Though I think she only said that cos she’s in it too LOL. You can always rely on your friends to bring you down to earth eh?

    And before we knew it, it was Friday and it was all over. Time to pack up the booth and say goodbye to old and new friends alike, buoyed by the knowledge that we’ll all be back to do it again next year, where I’m already looking forward to demonstrating the latest and greatest product line up from DevExpress. See you there!

  • XPO – Charting Local Weather Conditions #2

    In the last post on this topic I showed you how to pull down weather information from your local METAR station and persist it using XPO. In this post we’ll go ahead and use that persisted information to graph some interesting weather data.

    First thing we’re going to do is to add a new win forms project to our solution to provide a front end for our graphing application. We’ll add folders for Helpers, Model and Views too:

    image

    After that we’ll start the coding by pointing XPO at the Access database we created in the last post. We’ll do this in the form load event:

    private void MainView_Load(object sender, EventArgs e)
    {
        //GS - Connect to the access database created earlier
        XpoDefault.ConnectionString =
            AccessConnectionProvider.GetConnectionString("pathToYourAccessDatabaseFile");
    }

    Next we’ll create a UI that will allows us to graph three interesting pieces of weather data; wind speed by direction, temperature fluctuation throughout a day and the prevailing wind direction for this area:

    image

    Starting with wind speed by direction, what we’ll do is handle the button click event:

    private void btnNorth_Click(object sender, EventArgs e)
    {
        //GS - Display northerly wind speeds
        DisplayWindSpeedByDirectionWithTitle("Northerly Wind Speeds", "N");
    }

    by calling a method to display the data:

    private void DisplayWindSpeedByDirectionWithTitle(string title, string direction)
    {
        //GS - Get all the Northerly wind direction reports
        XPQuery<Report> reportQuery = new XPQuery<Report>(XpoDefault.Session);
    
        var reports = from r in reportQuery
                      where r.WindDirection.StartsWith(direction)
                      select r;
    
        //GS - Ask a helper method to create a chart from this info
        ChartControl chart = ChartHelper.CreateBarChartFromLinqResult("WindSpeed", "TimeStamp", title, reports);
    
        //GS - Ask a helper method to display this chart
        ViewHelper.DisplayChart(chart);
    }

    This method will firstly use a linq statement to get all the weather reports where the wind direction starts with the required direction: “N” for North; “S” for South; etc. The next thing it will do is call a helper method, passing in the chart title, the qualitative and quantitative properties of the Report object and the result of the linq query. Lastly, this method then calls another helper method in order to display the chart returned by the first helper method.

    Let’s have a look at this first helper method:

    public static ChartControl CreateBarChartFromLinqResult(
        string valueMember, 
        string dataMember, 
        string title, 
        IQueryable<Report> reports)
    {
        ChartControl chart = new ChartControl();
        chart.Titles.Add(new ChartTitle { Text = title });
        chart.Legend.Visible = false;
    
        //GS - Create an empty Bar series and add it to the chart.
        Series series = new Series(title, ViewType.Bar);
        chart.Series.Add(series);
    
        //GS - Supply the data
        series.DataSource = reports.ToList<Report>();
    
        //GS - Bind the data
        series.ArgumentScaleType = ScaleType.Qualitative;
        series.ArgumentDataMember = dataMember;
        series.ValueScaleType = ScaleType.Numerical;
        series.ValueDataMembers.AddRange(new string[] { valueMember });
    
        //GS - Dock the chart into its parent
        chart.Dock = DockStyle.Fill;
    
        //GS - Return the chart
        return chart;
    }

    This method is fairly simple, all it does is create a new chart object and add a series to it. We then attach our linq results as the data source and then tell the chart how to bind to that data. Finally, we set the dock style to fill and return the chart to the calling method.

    The second helper method is simpler still:

    public static void DisplayChart(ChartControl chart)
    {
        ChartViewer viewer = new ChartViewer();
        viewer.Controls.Add(chart);
        viewer.Show();
    }

    It takes in a chart object and instantiates a ChartViewer form before adding the chart to the form’s controls and then showing the form.

    So having explained how we do it, let’s have a look at the results, pressing the “North” button yields this chart:

    image

    The remaining “South”, “East” and “West” buttons are a repeat of the same pattern so there is no point in going over those.

    The next thing we are going to look at is charting the temperature fluctuations throughout the day. To do this we select a date and press the “Chart” button:

    image

    The event handler for the “Chart” button is shown below:

    private void bntChartWindSpeedByDay_Click(object sender, EventArgs e)
    {
        //GS - Get the selected data
        DateTime target = dateTimePicker1.Value;
        
        XPQuery<Report> reportQuery = new XPQuery<Report>(XpoDefault.Session);
    
        var reports = from r in reportQuery
                      where r.TimeStamp.Date == target.Date
                      select r;            
    
        //GS - If there is no matching data for the target date then we're done
        if (reports.Count<Report>() < 1)
            return;
    
        //GS - Ask a helper method to create a chart from this info
        ChartControl chart = ChartHelper.CreateBarChartFromLinqResult(
            "Temperature", "TimeStamp", 
            String.Format("Temperatures for: {0}", target.Date.ToLongDateString()), 
            reports);
    
        //GS - Ask a helper method to display this chart
        ViewHelper.DisplayChart(chart);
    }

    As you can see, we follow a similar pattern to that already discussed, whereby we execute a linq query against the XPO source, passing the result to a helper method which constructs and returns a chart object and we then pass that chart object to another helper function which displays the chart. The result of selecting a date and pressing the “Chart” button is shown below:

    image

    The last interesting piece of information that we are going to graph is the prevailing wind direction. The event handler for the “Show Prevailing Wind Direction” button is shown below:

    private void btnMeanWindDirection_Click(object sender, EventArgs e)
    {
        //GS - Fetch all the reports
        XPQuery<Report> reportQuery = new XPQuery<Report>(XpoDefault.Session);
    
        var reports = from r in reportQuery
                      select r;
    
        //GS - Ask a helper method to create a pie chart of the directionCounts
        ChartControl chart = ChartHelper.CreateWindDirectionPieChart(reports);
    
        //GS - Ask a helper method to display this chart
        ViewHelper.DisplayChart(chart);
    }  

    You will recognise this now familiar pattern of executing a linq statement and then passing the result to a helper method to construct and return a chart object before passing that chart object, in turn, to another helper method to display the chart. The only difference this time is the helper method which constructs the chart is different, let’s take a look at it now:

    public static ChartControl CreateWindDirectionPieChart(IQueryable<Report> reports)
    {
        ChartControl chart = new ChartControl();
        chart.Titles.Add(new ChartTitle { Text = "Prevailing Wind Direction" });
        chart.Legend.Visible = true;
    
        //GS - Create an empty Bar series and add it to the chart.
        Series series = new Series(String.Empty, ViewType.Pie3D);
        series.LegendPointOptions.PointView = PointView.ArgumentAndValues;
        series.PointOptions.ValueNumericOptions.Format = NumericFormat.Percent;
        chart.Series.Add(series);
    
        //GS - Supply the data
        List<WindDirectionCount> directionCounts = new List<WindDirectionCount>();
        directionCounts.Add(
            new WindDirectionCount
            {
                Direction = "North",
                Count = reports.Count<Report>(x => x.WindDirection.StartsWith("N"))
            });
    
        directionCounts.Add(
            new WindDirectionCount
            {
                Direction = "South",
                Count = reports.Count<Report>(x => x.WindDirection.StartsWith("S"))
            });
    
        directionCounts.Add(
            new WindDirectionCount
            {
                Direction = "East",
                Count = reports.Count<Report>(x => x.WindDirection.StartsWith("E"))
            });
    
        directionCounts.Add(
            new WindDirectionCount
            {
                Direction = "West",
                Count = reports.Count<Report>(x => x.WindDirection.StartsWith("W"))
            });
    
    
        series.DataSource = directionCounts;
    
        //GS - Bind the data
        series.ArgumentScaleType = ScaleType.Qualitative;
        series.ArgumentDataMember = "Direction";
        series.ValueScaleType = ScaleType.Numerical;
        series.ValueDataMembers.AddRange(new string[] { "Count" });
    
        //GS - Dock the chart into its parent
        chart.Dock = DockStyle.Fill;
    
        //GS - Return the chart
        return chart;
    }

    This helper method is similar, but a little different, to the previous one. Again it creates a chart object and adds a series to it. The series is a little different this time though:

    //GS - Create an empty Pie series and add it to the chart.
    Series series = new Series(String.Empty, ViewType.Pie3D);
    series.LegendPointOptions.PointView = PointView.ArgumentAndValues;
    series.PointOptions.ValueNumericOptions.Format = NumericFormat.Percent;
    chart.Series.Add(series);

    As you can see we are creating a pie chart this time and so we use the PointView.ArgumentAndValues enum to specify that we want to show the wind direction as well as the values and the NumericFormat.Percent enum to specify that we want to show the values as a percentage of the total.

    We then go on to count the number of weather reports that state the wind was from a particular direction (N, NNW all count as North etc.) and we go ahead and store this information in a list of objects we created for that purpose. These objects are very simple in nature and they are defined like so:

    using System;
    
    namespace XPOWeather2
    {
        public class WindDirectionCount
        {
            public string Direction { get; set; }
            public int Count { get; set; }        
        }
    }

    Then we set the data and bind to is as before, specifying the qualitative and the quantitative properties, before finishing by asking the same helper method to display the chart. The result of all this is shown below:

    image

    As you can see, the prevailing wind direction in this part of the world is shown as westerly (which it actually is!).

    Well that about wraps it up for this post, ‘til the next time, happy XPOing!

  • XPO – Charting Local Weather Conditions #1

    All the talk of climate change means that there are a lot of people interested in the weather right now, and rightly so. In the first of two posts on this topic, I’m going to show you how to sample weather data, on on hourly basis, and then use XPO to store that data. In the second post we will look at charting that data. But first, how do you get weather information if you don’t have a weather station in your garden? Well, the answer is, you can use other people’s data.

    You may not know this, but airports, military basis and other weather stations report weather conditions on an hourly basis and this information is held by the National Oceanic and Atmospheric Administration and is made available to the public. This system is known as METAR. It is this system we will make use of to sample the weather reports that we need.

    The weather reports are filed under the ICAO code for the station they come from, so the first thing we need to do is to identify which station we are going to use and what the code is for that station. I live only a few miles from Dundee Airport and so I’m going to use that for my reports. I need to find the ICAO code for Dundee Airport and a simple Google search tells me the code is EGPN.

    Next we need to know where to retrieve the report from. METAR reports are encoded and are, frankly, a pain in the bum to decode. Luckily for us, those nice people at the NOAA have taken care of that for us. They file decoded METAR reports at: ftp://tgftp.nws.noaa.gov/data/observations/metar/decoded/. All we have to do is to pull back the text file EGPN.txt from that location, parse it and store that information. So let’s go ahead now and build an application to do that.

    Create a standard console application:

    image

    Now let’s create a METAR helper class with a function to fetch the report for us:

    public static void GetMetarDataForCode(string code)
    {
        if (String.IsNullOrEmpty(code))
            return;
    
        const string ADDRESS =
            @"ftp://tgftp.nws.noaa.gov/data/observations/metar/decoded/";
    
        string url = String.Format("{0}{1}.TXT", ADDRESS, code.ToUpper());
    
        string report;
    
        try
        {
            report = new WebClient().DownloadString(url);
            StoreMetarData(report);
        }
    
        catch (WebException wex)
        {
            Logger.Log(String.Format("{0}: {1}", DateTime.Now, wex.Message));
        }           
    }

    Which we’ll call from our program entry point:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace XPOWeather1
    {
        class Program
        {
            static void Main(string[] args)
            {
                const string METAR_CODE = "EGPN";
                MetarHelper.GetMetarDataForCode(METAR_CODE);
            }
        }
    }

    Next, let’s define an model class to hold our report. As this class extends XPObject we’ll need to add references to DevExpress.Data and DevExpress.Xpo at this point. We are not interested in all of the data in the report, so something like this should suffice:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using DevExpress.Xpo;
    
    namespace XPOWeather1.Model
    {
        public class Report: XPObject
        {
            public Report(Session session)
                : base(session)
            { }
    
            private DateTime timeStamp;
            public DateTime TimeStamp
            {
                get
                {
                    return timeStamp;
                }
                set
                {
                    SetPropertyValue("TimeStamp", ref timeStamp, value);
                }
            }
    
            private string skyConditions;
            public string SkyConditions
            {
                get
                {
                    return skyConditions;
                }
                set
                {
                    SetPropertyValue("SkyConditions", ref skyConditions, value);
                }
            }
    
            private string visibility;
            public string Visibility
            {
                get
                {
                    return visibility;
                }
                set
                {
                    SetPropertyValue("Visibility", ref visibility, value);
                }
            }
    
            private string windDirection;
            public string WindDirection
            {
                get
                {
                    return windDirection;
                }
                set
                {
                    SetPropertyValue("WindDirection", ref windDirection, value);
                }
            }
    
            private int windSpeed;
            public int WindSpeed
            {
                get
                {
                    return windSpeed;
                }
                set
                {
                    SetPropertyValue("WindSpeed", ref windSpeed, value);
                }
            }
    
            private int temperature;
            public int Temperature
            {
                get
                {
                    return temperature;
                }
                set
                {
                    SetPropertyValue("Temperature", ref temperature, value);
                }
            }
        }
    }

    Having done that, we can write the function to create and store the Report:

    private static void StoreMetarData(string reportText)
    {
        if (String.IsNullOrEmpty(reportText))
            return;
    
        using (UnitOfWork uow = new UnitOfWork())
        {
            Report report = new Report(uow) 
            { 
                SkyConditions = GetSkyConditionsFromReportText(reportText), 
                Temperature = GetTemperatureFromReportText(reportText), 
                TimeStamp = GetTimeStampFromReportText(reportText), 
                Visibility = GetVisibilityFromReportText(reportText), 
                WindDirection = GetWindDirectionFromReportText(reportText), 
                WindSpeed = GetWindSpeedFromReportText(reportText) 
            };
    
            uow.CommitChanges();
        }
    }

    Here are the definitions for those helper functions, just for completeness sake:

    private static string GetSkyConditionsFromReportText(string reportText)
    {
        string result = String.Empty;
        if (String.IsNullOrEmpty(reportText))
            return result;
    
        return GetInformationFromReportText("Sky conditions:", reportText);
    }
    
    private static string GetInformationFromReportText(string key, string reportText)
    {
        string result = String.Empty;
        if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(reportText))
            return result;
        
        return reportText.Split(Environment.NewLine.ToCharArray()).
            First<string>(x => x.Contains(key)).
                Split(":".ToCharArray())[1].Trim();
    }
    
    private static int GetTemperatureFromReportText(string reportText)
    {
        if (String.IsNullOrEmpty(reportText))
            return 0;
    
        return Convert.ToInt32(GetInformationFromReportText(
            "Temperature:", reportText).Split("(".ToCharArray())[1].
                Split(")".ToCharArray())[0].Split(" ".ToCharArray())[0]);
    }
    
    private static DateTime GetTimeStampFromReportText(string reportText)
    {
        DateTime result = DateTime.MinValue;
        if (String.IsNullOrEmpty(reportText))
            return result;
    
        string temp = reportText.Split(Environment.NewLine.ToCharArray()).
            First<string>(x => x.Contains("UTC")).Split("/".
                ToCharArray())[1].Replace(".", "/");
    
        string temp2 = temp.Substring(0, temp.Length - 6);
        string temp3 = temp.Substring(temp.Length - 6);
        string temp4 = String.Format("{0}:{1}", temp2, temp3);
    
        return DateTime.Parse(temp4.Remove(temp4.Length - 3, 3).Trim());
    }
    
    private static string GetVisibilityFromReportText(string reportText)
    {
        string result = String.Empty;
        if (String.IsNullOrEmpty(reportText))
            return result;
    
        return GetInformationFromReportText("Visibility:", reportText);
    }
    
    private static string GetWindDirectionFromReportText(string reportText)
    {
        string result = String.Empty;
        if (String.IsNullOrEmpty(reportText))
            return result;
    
        return reportText.Split(Environment.NewLine.ToCharArray()).
            First<string>(x => x.StartsWith("Wind:")).Split(":".
                ToCharArray())[1].Split("(".ToCharArray())[0].Trim().
                    Split(" ".ToCharArray())[2];
    }
    
    private static int GetWindSpeedFromReportText(string reportText)
    {
        if (String.IsNullOrEmpty(reportText))
            return 0;
    
        return Convert.ToInt32(reportText.Split(Environment.NewLine.ToCharArray()).
            First<string>(x => x.StartsWith("Wind:")).Split(")".
                ToCharArray())[1].Trim().Split(" ".ToCharArray())[1]);
    }

    The last thing we have to do in this simple little application is to create a Logger helper class and provide a Log helper function, so we can log any web exceptions as we’re going to run this unattended:

    using System;
    using System.IO;
    
    namespace XPOWeather1
    {
        public class Logger
        {
            public static void Log(string message)
            {
                File.AppendAllText(@".\Files\Log.txt", message);
            }
            
        }
    }

    Now that we are finished, our project looks like this:

    image

    Running this application gives us a nice record of the weather, updated by the Dundee Airport weather station on an hourly basis:

    image

    The last thing we need to do in this post is to set up our application to run hourly and pick up the weather reports. We’ll do this by using the Task Scheduler:

    image

    In the next post, we’ll look at retrieving this information and charting some important weather information. Until then… happy XPOing!

  • Cloud Computing Could Save Your Life…

    Well okay maybe not your life, because let’s face it, you are probably a well paid programmer living in the developed world. But watching the stories of the earthquake in Sumatra and the tsunami in Samoa over the last few days got me thinking that cloud computing could be a real life saver in these situations. How so? Well, did you know that an estimated $6.2 billion of donated medical supplies and 96 billion pounds of food never make it to the people who need them each year, simply because the technology and skills required for effective distribution are not available?

    All the experts will tell you that the faster you get medical supplies and general “aid” to the disaster stricken regions then the more lives you can save. There will also be disaster relief plans set up ready to be put into action at a moment’s notice. These plans, I’m sure, are very detailed and cover every foreseeable eventuality. Of course, any soldier will tell you that it doesn’t matter how good your planning is, no plan survives first contact with the enemy. The enemy in this case is going to be the disaster itself. As soon as relief workers arrive on the scene, “the plan” is going to have to be changed to fit the particular needs of the survivors. Systems are going to have to be in place  to handle things like causality bureaux, missing persons, food/water distribution and other general logistical requirements. All this information is going to have to be fed back to HQ so that “the plan” can be amended as required.

    All of this is perfectly obvious. The trouble is that… well the place is a disaster area, right? That’s why the relief workers are there in the first place. That causes two problems. Firstly, the lack of power on scene to run these systems and two, the relief agencies would have to ship all this kit out to the location, when what they really need to be doing is shipping food/medicine/rescue equipment etc. This is where cloud computing comes in. With all of the databases and “heavy computing” being abstracted out to the cloud, it now becomes possible for the relief workers to use much smaller computers – perhaps even PDAs which they can fit in their pockets, or in their back packs.

    Now there isn’t likely to be any power on scene, but that is not an issue as they will be shipping generators anyway to power the lights and other rescue equipment so that’s okay. The other issue of course is connectivity, a cloud based solution is no use if you can’t connect to it. There are ways to overcome this problem however. If there are 3G towers in the area, and they have survived, then you can use products like this one to connect to them without a hotspot. If there is no network left then you can use a portable satellite solution, these things are not particularly big or heavy to ship.

    Taking things a step further, instead of shipping the computers, wouldn’t it be cool if they could just ship the software instead? Well there is no reason why they couldn’t do that too. All they’d have to do is to write these systems for Linux – using Mono for example – then spin a LiveCD version of Linux, including the client systems. These respins could be loaded onto CD or USB sticks and carried to the scene in the pockets of rescue workers. Once on scene, they’d need to find a building with power and connectivity, but if they could – or if they knew of such a building before they left – they could just plug in the usb sticks, power up the machines and away they’d go. Once they were finished, just power down the machines again and then it’s as if they’d never been there. 

    Okay, so I’m sure there are lots of little niggly issues around my great plan, but I’m sure they could be resolved if enough thought was put to them. People are realising that software is required at the scene of these disasters. After hurricane Katrina the Department of Homeland Security recommended that a Chief Logistics Officer be appointed. Part of his remit is to procure software of this kind and a cloud based solution would appear, to me, to be the obvious one. What do you think?

  • XAF – Project Management Application #10

    Phew, well I’m back from a hectic week at BASTA and all set to post the latest in our series on creating an project management application using XAF. So, what are we going to do in this post? Well, first of all our status changing algorithm isn’t very good is it? For a start you can’t go backwards to a previous status and it assumes that their is only one possible status to go forward to. These two things make our model of status pretty unrealistic. So, let’s do something about that.

    A state machine or workflow solution sounds best suited to fixing this problem and, as I’ve said, there are plans for such things, but for now we’ll allow users to decide which status suits best. We’ll get rid of the “Change status” button and make the status list editable. To do this, we’ll remove the “protected” keyword from the TaskBase’s Status property setter declaration.

    Now we’re able to change the status manually:

    1

     2

    Okay, well that sort of worked, but there are a couple of issues:

    1. The New and Clear buttons are displayed in the lookup.
    2. Only the statuses corresponding to the current task type should be displayed, not all of them.
    3. Statuses are sorted in the alphabetical order instead of their logical sequence;

    So, let’s write a test for the New button to ensure it’s unavailable:

    #DropDB XProjectEasyTest

    #Application XProjectWin

    #Application XProjectWeb

    *Action Navigation(Project Tasks)

    *Action New

    #IfDef XProjectWin

    *ExecuteEditorAction Status

    !ActionAvailable New

    #EndIf

    #IfDef XProjectWeb

    !ExecuteEditorAction Status(Add)

    #EndIf

    Looking at this test, you can see that the syntax is different between the web and winforms actions; oops. :-) Don’t worry we are aware of this and will fix it. Running that test at the moment fails, not surprisingly. To remove the New button we’ll create a new AvailableStatuses property on the TaskBase Class:

    public abstract class TaskBase : BaseObject {
    …
        private XPCollection<TaskStatus> availableStatuses;
    
        [Browsable(false)]
        public XPCollection<TaskStatus> AvailableStatuses {
            get {
                if(availableStatuses == null) {
                    availableStatuses = new XPCollection<TaskStatus>(StatusSet.Items);
                    availableStatuses.BindingBehavior = CollectionBindingBehavior.AllowNone;
                }
    
                return availableStatuses;
            }
        }
    …
    }

    Check out the [Browsable(false)] attribute above, using this means that the property will not be visible in the UI. Meantime, the AvailableStatuses field will hold a local status collection. We’ll set this property’s BindingBehavior to CollectionBindingBehavior.AllowNone, so that the New button won’t be available in the AvailableStatuses lookup. Also note that the availableStatuses field is initialized only once when its value is first accessed. Now let’s limit the statuses displayed in the lookup to those that belong to the current task type; to do that we apply the DataSourceProperty("AvailableStatuses") attribute to the Status property:

    [Persistent, DataSourceProperty("AvailableStatuses")]
    public TaskStatus Status {
        get { return GetPropertyValue<TaskStatus>("Status"); }
        set { SetPropertyValue<TaskStatus>("Status", value); }
    }

    Launching the application, we see that the “new” button has gone and the status list looks much better:

    3

    4

    Running the functional test, above, now passes as a result of this change. :-)

    The next thing we are going to do is to nail that “Clear” button problem, we need to remove it so that the Status can’t be set to NULL. We’ll also write a unit test to check this:

    [Test]
    public void Status_CannotAssignNull() {
        TestProjectTaskClass task = new TestProjectTaskClass(Session);
        Assert.IsNotNull(task.Status);
    
       try {
            task.Status = null;
            Assert.Fail();
        }
        catch (ArgumentNullException){ }
    }

    No surprise, the test fails. So let’s modify the Status property a little:

    public TaskStatus Status
    {
        get { return GetPropertyValue<TaskStatus>("Status"); }
        set
        {
    
            if (!IsLoading)
                Guard.ArgumentNotNull(value, "Status");
    
            SetPropertyValue<TaskStatus>("Status", value);
        }
    }

    Running the test again shows it passes. Ah, don’t you just love the little victories? :-)

    Now we need to remove the “Clear” button from the Windows Forms application. To do this we’ll add a new TaskStatusLookupController View Controller. We want this Controller to be activated only for the Status lookup and so we’ll set its TargetViewId property to TaskStatus_LookupListView. We’ll write the code to remove the “Clear” button in the Controller’s OnActivated method:

    public class TaskStatusLookupController : ViewController
    {
        public TaskStatusLookupController()
        {
            TargetViewId = "TaskStatus_LookupListView";
        }
    
        protected override void OnActivated()
        {
            base.OnActivated();
    
            LookupWindowController standardController = Frame.GetController<LookupWindowController>();
            if (standardController != null)
                standardController.Active.SetItemValue("TaskStatusLookupController", false);
        }
    }

    As you can see, the “Clear” button is now gone:

    5

    Now, let’s go ahead and remove the “Clear” button from the ASP.Net application. Umm… yeah… Turns out there’s no easy way to do this at the moment, so we’ll create a suggestion for the XAF team to write such functionality, and quietly move on. :-)

    Moving on… let’s fix the issue of the alphabetical order of the statuses. We’ll replace our enumerations with string arrays that contain statuses in the required order and we’ll modify the UpdateDatabase method in the TaskBase class to achieve this:

    protected static void UpdateDatabase(Session session, string[] statusNames, string statusSetName)
    {
        TaskStatusSet statusSet = FindTaskStatusSet(session, statusSetName);
    
        if (statusSet == null)
        {
            TaskStatus[] statusList = new TaskStatus[statusNames.Length];
            statusSet = new TaskStatusSet(session, statusSetName);
    
    
            TaskStatus nextStatus = null;
            for (int j = statusNames.Length - 1; j >= 0; j--)
            {
                string statusName = statusNames[j];
                statusList[j] = new TaskStatus(session, statusSet, nextStatus, statusName);
                nextStatus = statusList[j];
            }
    
            statusSet.FirstStatus = statusList[0];
            statusSet.LastStatus = statusList[statusList.Length - 1];
    
            statusSet.Save();
        }
    }

    We also need to change the initialisation code of our Task:

    public class ProjectTask : TaskBase {
        public static readonly string[] InitialStatusNames = new string[] { "Draft", 
            "NotStarted", 
            "InProgress", 
            "Paused", 
            "Completed" };
    …
        public new static void UpdateDatabase(Session session) {
            UpdateDatabase(session, InitialStatusNames, StatusSetName);
        }
    …
    }

    Having made this change, I’ll leave you to remove the enumerations and amend the unit tests accordingly. Also, we need to amend the AvailableValuesForTaskStatus functional test. Change:

    !FillForm

    Status = Completed

    To:

    *FillForm

    Status = Completed

    To change the sort order of the statuses in the lookup we’ll add an Index property to the TaskStatus class. Since we are going to use indexes instead of an ordered list, we’ll remove the Next property. In the TaskStatus constructor we’ll replace the nextStatus parameter with the index parameter:

    public class TaskStatus : BaseObject {
     
        public TaskStatus(Session session, TaskStatusSet owner, int index, string name) : base(session) {
            Guard.ArgumentNotNull(owner, "owner");
            Guard.ArgumentNotNullOrEmpty(name, "name");
            Owner = owner;
            Name = name;
            Index = index;
        }
    
        public int Index {
            get { return GetPropertyValue<int>("Index"); }
            set { SetPropertyValue<int>("Index", value); }
        }
    ...
    }

    We’ll also initialize the new field in the TaskBase class’ UpdateDatabase method:

    protected static void UpdateDatabase(Session session, string[] statusNames, string statusSetName)
    {
        TaskStatusSet statusSet = FindTaskStatusSet(session, statusSetName);
    
        if (statusSet == null)
        {
            TaskStatus[] statusList = new TaskStatus[statusNames.Length];
            statusSet = new TaskStatusSet(session, statusSetName);
    
    
            for (int j = 0; j < statusNames.Length; j++)
            {
                statusList[j] = new TaskStatus(session, statusSet, j, statusNames[j]);
            }
    
            statusSet.FirstStatus = statusList[0];
            statusSet.LastStatus = statusList[statusList.Length - 1];
    
            statusSet.Save();
        }
    }

    Now we must get rid of the NextStatus button and the TaskBase class’ SetNextStatus method. We’ll remove this method as well as the ChangeTaskStatusController Controller and we’ll remove some unit tests which are now useless. For this purpose we’ll update and move the correct status order checking logic from the descendant tasks tests to the TaskBase tests:

    [Test]
    public void UpdateDatabase_CorrectStatusSequence()
    {
        TaskStatusSet statusSet = TestProjectTaskClass.FindTaskStatusSet(Session, TestProjectTaskClass.StatusSetName);
        Assert.IsNotNull(statusSet);
    
        List<string> statusList = new List<string>(TestProjectTaskClass.InitialStatusNames);
        foreach (TaskStatus currentStatus in statusSet.Items)
        {
            Assert.AreEqual(statusList.IndexOf(currentStatus.Name), currentStatus.Index);
        }
    }

    Now we’ll configure the order of the items in the lookup editor for the status property. In the Model Editor open the TaskStatus_LookupListView node and add a new column for the Index property, disable sorting for the Name property and enable sorting for the Index property (set the required value for the SortOrder attribute):

    6

    Right, so let’s fire up our applications now:

    7

     8

    Yay! The winform application looks great and the webform application… meh, not so much. Looks like we’ve found a bug. The list of objects in Simple mode (ComboBox edit) is always sorted by Name regardless of what the settings in ListView node are.

    Well, while that bug is getting fixed we’ll go ahead and introduce the capability to modify/edit status sets from the UI. First, we’ll apply the [NavigationItem("Settings")] attribute to the TaskStatusSet class this will mean that we can now edit the StatusSet objects in the UI:

    9

     10

    Hmm, well it’s a start but there are still issues: the Name property is editable, which can cause a mismatch between a task type and the corresponding status set. To fix this, we’ll make the TaskStatusSet’s Name property setter protected. Additionally we’ll apply the Persistent attribute, so that XPO will persist this property:

    [Persistent]
    public string Name
    {
        get { return GetPropertyValue<string>("Name"); }
        protected set { SetPropertyValue<string>("Name", value); }
    }

    Also, the “Task Status Set” caption in the navigation control isn’t very useful. We’ll rename it “Task Statuses”. To do this, add the DisplayName attribute to the TaskStatusSet class:

    [NavigationItem("Settings"), System.ComponentModel.DisplayName("Task Statuses")]
    public class TaskStatusSet : BaseObject
    {
    …
    }

    Next, the default layout of controls isn’t very convenient. For example, the Name property should be displayed first. We’ll use the Model Editor to fix this. Double click the Model.DesignedDiffs.xafml file in the module project, navigate to the Application\Views\TaskStatusSet_DetailView\Layout node. Right click the design surface and choose “Customize Layout”:

    11

    Statuses should be sorted by their indexes. To configure the sort order we’ll use the Model Editor (again <grin>). If we navigate to the TaskStatusSet_Items_ListView node, we’ll see that by default the Name property is used for sorting purposes (as it’s the default property). We want to sort by the Index property. To achieve this, change the SortIndex and SortOrder attributes’ values for the Name and Index properties. To check the default sorting we’ve written a functional test - StatusSet_Items_DefaultSortByIndex.ets which now passes:

    #DropDB XProjectEasyTest

    #Application XProjectWin

    #Application XProjectWeb

    *Action Navigation(Task Statuses)

    *ProcessRecord

    Name = ProjectTask

    *CheckTable Items

    Columns = Index

    Row[1] = 0

    Row[2] = 1

    Row[3] = 2

    Row[4] = 3

    Row[5] = 4

    Users shouldn’t be able to create/delete StatusSets, we should only allow editing.Again we invoke the Model Editor and set the AllowNew and AllowDelete attribute to False for the TaskStatusSet_ListView and TaskStatusSet_DetailView Views. And again we write a functional test to check that the New and Delete Actions are disabled (StatusSet_DisabledNewDeleteActions.ets script file):

    #DropDB XProjectEasyTest

    #Application XProjectWin

    #Application XProjectWeb

    *Action Navigation(Task Statuses)

    !ProcessRecord

    Name = ProjectTask

    Action = Delete

    !ActionAvailable TaskStatusSet.New

    ;using ProjectTask's existing statuses

    *ProcessRecord

    Name = ProjectTask

    !ActionAvailable TaskStatusSet.Delete

    We still need to filter the drop-down lookups for the FirstStatus and LastStatus properties. To do this we’ll apply the [DataSourceProperty("Items")] attribute to both properties of the TaskStatusSet class:

    public class TaskStatusSet : BaseObject
    {
    …
    
        [DataSourceProperty("Items")]
        public TaskStatus FirstStatus {
            get { return GetPropertyValue<TaskStatus>("FirstStatus"); }
            set { SetPropertyValue<TaskStatus>("FirstStatus", value); }
        }
    
        [DataSourceProperty("Items")]
        public TaskStatus LastStatus {
            get { return GetPropertyValue<TaskStatus>("LastStatus"); }
            set { SetPropertyValue<TaskStatus>("LastStatus", value); }
        }
    }
    Phew, well I don’t know about you, but I’ve had quite enough for this post. If you want to play along at home, you can download the code from here. Next time, we’ll integrating his application with another internal application, in the meantime, if you are doing anything interesting with XAF or XPO drop me a line and tell me all about it and… happy XAFing!
  • Sorry I’m not Here Right Now, Please Leave a Message After the Tone…

    As you may have guessed from the title, I’m not around right now. In fact I’ll be exhibiting the DevExpress product line at the BASTA conference in Mainz, Germany. If you are going to be attending the conference please stop by the booth and say hello, it’s always nice to meet readers and “put a face to a name”. If you are not going to be in Germany for the conference, then you can keep up to date with what is going on there by following me on Twitter (@garyshort).

    Normal service, well as normal as it gets around here anyway, will be resumed next Monday.

  • Successful Companies Follow Their Customers

    By 2010 the baby boomers will have given way to Generation Y and so your target marketing demographic will have moved on to a generation who are far more au fait with social technology than the generation that preceded them (96% of them have joined a social network). What will that mean for your future marketing strategies? Well for one thing, you are going to have to move your marketing online, if you haven’t already. Social networking has overtaken pornography as the #1 activity on the Internet, that’s where your customers are and that’s where you have to be too.

    Not only are your customers moving online, but the rate at which technology is reaching critical mass is increasing: whist it took radio 38 years to gain 50 million users, Facebook gained 100 Million users in less than 8 months and, incredibly, Apple gained 1 billion downloads from its AppStore in just 9 months. You may not think your company/product needs to be on Facebook, but with its 300 million active users, if it were a country, it would be the fourth largest; why would you want to shut yourself off from a market of that size?

    Meh, I hear you say, “we’re already online, we have a blog, Google knows where we are and therefore our customers know where we are” Really? Well that might be true, but did you also know that YouTube is the second largest search engine in the world? You have to think beyond producing the same bland, text based content as your competitors; you must start to innovate with other media, like video for example, to get your message across. Watching video content online is popular; Hulu, for, example, has grown from 63 million streams to 373 million in 1 year.

    Of course, when I say you must move your marketing online in order to reach the next generation of the target advertising demographic, I don’t just mean advertise online. Why not, I hear you ask? Simple, no one believes online advertising. Well, okay, that might be a bit of an exaggeration; the fact is only 33% of consumers trust online banner ads, versus 90% who trust word of mouth recommendations from friends. So the message is clear, if you want to sell to the next generation you have to talk to them.

    Talk to them? But when? Where? Well the answer to that is simple: anytime and anywhere. 17% of Brits and 15% of Americans now social network via mobile phone your customers are never more than a few key presses away from their blogs; think about that the next time one of your staff members upsets a customer. There are nearly 200,000,000 blogs on the Internet with 54% of bloggers posting daily and 26% of bloggers blogging about products or brands. That means there is someone talking about your products. The time for deciding if you want your product to be talked about has gone – the only thing left to decide is: do you want to try and shape that conversation?

    Still not convinced that social media is the future of advertising and therefore the future of your company? What about print advertising I hear you say, that still works… doesn’t it? Not so much. Newspaper readership fell from 34% to 25% between 2006 and 2009 and the trend lines have been heading south for years. The truth is the smart money has moved online.

    Okay, so I’ve convinced you that you have to at least “dip your toe in the water” of social marketing, the question now is: how do you get started? Well, there are some great books here, here and here. Read them, put the lessons learned into practice and you’ll not go too far wrong. You should also check out these great blogs:

    http://sethgodin.typepad.com/
    http://www.chrisbrogan.com/
    http://www.steverubel.com/
    http://blog.guykawasaki.com/
    http://www.buzzmachine.com/

    But in the end, the best advice I can offer you is to get out there and do it, be honest, and just talk to your customers; they’re already talking about about you. :-)

  • XAF – Project Management Application #9

    In this edition of our continuing series of blog posts on how to create a project management application in XAF, we will be progressing with the statuses functionality within our application. This time around, we’ll be adding a new task type, cleaning up our code a little bit and adding some logic around the rules of marking a task as complete.

    The first thing we are going to do is to add a new engineering task type, with it’s own status set:

    [DefaultClassOptions]
    [NavigationItem("Planning")]
    public class EngineeringTask : BaseObject {
        public EngineeringTask(Session session) : base(session){     }
    }
    public enum EngineeringTaskStatus {
        Draft,
        Planned,
        Elaboration,
        Development,
        Documenting,
        Completed
    }

    Now that we have both a ProjectTask and an Engineering task, it stands to reason that there is going to be duplicate code, i.e. code that appears both in the ProjectTask and the EngineeringTask. The duplication of code can only lead to errors at maintenance time, and so we should create a common base class, from which each class can derive and place all the common code there, leaving the subclasses to house the specific task code:

    public abstract class TaskBase : BaseObject
    {
        public TaskBase(Session session)
            : base(session)
        {
        }
    }

    Now let’s move the common code from the ProjectTask into the TaskBase class:

    [RuleCriteria("ProjectTask-EndDateMustBeAfterStartDate", DefaultContexts.Save, "EndDate > StartDate")]
    public abstract class TaskBase : BaseObject {
        public const string StatusSetName = "TaskBase";
    
        protected override XPCollection<T> CreateCollection<T>(DevExpress.Xpo.Metadata.XPMemberInfo property) {
            XPCollection<T> col = base.CreateCollection<T>(property);
            if(property.Name == "TaskWork")
                col.CollectionChanged += new XPCollectionChangedEventHandler(col_CollectionChanged);
            return col;
        }
        protected void col_CollectionChanged(object sender, XPCollectionChangedEventArgs e) {
            if(e.CollectionChangedType == XPCollectionChangedType.BeforeRemove) {
                RemainingHours += ((TaskWork)e.ChangedObject).HoursSpent;
                UpdateHoursSpent();
            }
        }
        protected abstract string GetStatusSetName();
        protected static void UpdateDatabase<EnumerationType>(Session session, string statusSetName) {
            if(!typeof(EnumerationType).IsEnum)
                throw new ArgumentOutOfRangeException();
    
            TaskStatusSet statusSet = FindTaskStatusSet(session, statusSetName);
    
            if(statusSet == null) {
                string[] statusNames = Enum.GetNames(typeof(EnumerationType));
                TaskStatus[] statusList = new TaskStatus[statusNames.Length];
                statusSet = new TaskStatusSet(session, statusSetName);
    
    
                TaskStatus nextStatus = null;
                for(int j = statusNames.Length - 1; j >= 0; j--) {
                    string statusName = statusNames[j];
                    statusList[j] = new TaskStatus(session, statusSet, nextStatus, statusName);
                    nextStatus = statusList[j];
                }
    
                statusSet.FirstStatus = statusList[0];
                statusSet.LastStatus = statusList[statusList.Length - 1];
    
                statusSet.Save();
            }
        }
    
        public TaskBase(Session session)
            : base(session) {
        }
        public override void AfterConstruction() {
            base.AfterConstruction();
    
            this.StatusSet = FindTaskStatusSet(Session, GetStatusSetName());
            this.Status = this.StatusSet.FirstStatus;
        }
        public static void UpdateDatabase(Session session)
        {
            throw new NotImplementedException("Inheritor class has to override (witn 'new' keyword) and implement this method");
        }
        public static TaskStatusSet FindTaskStatusSet(Session session, string statusSetName) {
            return session.FindObject<TaskStatusSet>(new BinaryOperator("Name", statusSetName));
        }
        public void SetNextStatus() {
            if(this.Status == this.StatusSet.LastStatus) {
                throw new InvalidOperationException();
            }
    
            this.Status = this.Status.NextStatus;
        }
    
        //properties (unchanged)
       ...
    }

    So let’s see what it was that we did here. We didn’t apply the “DefaultClassOptions” and “NavigationItem("Planning")” attributes because the class cannot be instantiated and so shouldn’t have a navigation item. We’ve also introduced a new constant (StatusSetName) which identifies the status set corresponding to the current task type, descendants of this class will have to override this to specify their own StatusSet. Then, We defined a new function – UpdateDatabase, which will be used by TaskBase descendants to set the required status set types. As you can see to make sure that this method cannot be used unless overridden, we throw an exception in it. Having done that, we’ve marked the old UpdateDatabase method as protected and made it generic. The generic type parameter specifies the required status set. In the method we also check that the passed type is an enumeration. Also, the FindTaskStatusSet method now takes an additional parameter which specifies the status set name. We’ve added this new status set name parameter to the FindTaskStatusSet and UpdateDatabase methods because these method implementations reside in the base class, while the methods are going to be called from descendants which can use different status sets.  To move the AfterConstruction method to the base class we’ve replaced the call to the StatusSetName property with a call to the GetStatusSetName method. This method is abstract – so, descendants have to implement it.

    Now that we’ve defined our TaskBase class, let’s go back and define our new ProjectTask class:

    [DefaultClassOptions]
    [NavigationItem("Planning")]
    public class ProjectTask : TaskBase
    {
        public new const string StatusSetName = "ProjectTask";
    
        public ProjectTask(Session session)
            : base(session)
        {
        }
        public new static void UpdateDatabase(Session session)
        {
            UpdateDatabase<ProjectTaskStatus>(session, StatusSetName);
        }
        protected override string GetStatusSetName()
        {
            return StatusSetName;
        }
    }
    
    public enum ProjectTaskStatus
    {
        Draft,
        NotStarted,
        InProgress,
        Paused,
        Completed
    }

    Looking at this code we can see what we have to do when we derive a new task type:

    1. Override the StatusSetName constant
    2. Implement the GetStatusSetName method
    3. Override the the public UpdateDatabase method to call the base protected UpdateDatabase method.

    Now that we know what needs to be done, let’s go ahead and define our EngineeringTask class:

    [DefaultClassOptions]
    [NavigationItem("Planning")]
    public class EngineeringTask : TaskBase
    {
        public new const string StatusSetName = "EngineeringTask";
    
        public EngineeringTask(Session session)
            : base(session)
        {
        }
    
        public new static void UpdateDatabase(Session session)
        {
            UpdateDatabase<EngineeringTaskStatus>(session, StatusSetName);
        }
    
        protected override string GetStatusSetName()
        {
            return StatusSetName;
        }
    }
    
    public enum EngineeringTaskStatus
    {
        Draft,
        Planned,
        Elaboration,
        Development,
        Documenting,
        Completed
    }

    Oh, and we also have to change all the existing ProjectTask class references to the TaskBase class. If we now launch the application, we’ll be able to create a EngineeringTask object and see that it has all the fields of the TaskBase class, but the status set is differently (you can see the “Elaboration” status on the screenshot).

    1

      2

    One thing though, most of the fields are declared in the TaskBase class, and so they are displayed in the “Task Base” (and not the “Engineering Task”) layout group. This looks a bit odd, so let’s correct it. To do that we’ll need to edit the Application Model for the module project. We’ll rename this layout group for the EngineeringTask and ProjectTask classes:

    <DetailView ID="EngineeringTask_DetailView">
      <Layout>
        <LayoutGroup ID="Main">
          <LayoutGroup ID="SimpleEditors">
            <LayoutGroup ID="TaskBase" Caption="Engineering Task" />
          </LayoutGroup>
        </LayoutGroup>
      </Layout>
    </DetailView>
    <DetailView ID="ProjectTask_DetailView">
      <Layout>
        <LayoutGroup ID="Main">
          <LayoutGroup ID="SimpleEditors">
            <LayoutGroup ID="TaskBase" Caption="Project Task" />
          </LayoutGroup>
        </LayoutGroup>
      </Layout>
    </DetailView>

    Now, that looks a bit better, doesn’t it?

    3

     4

    Okay, so the TaskBase class is abstract but we’ve still got to test it and to do that, we’re going to need a tests subclass. To create one we’ll create a new TaskBaseTests class and copy the ProjectTaskTests’s SetUp method to the TaskBaseTests, and change ProjectTask.UpdateDatabase(Session) to TestProjectTaskClass.UpdateDatabase(Session). Then move all the common functionality tests, changing the ProjectTask class name to TestProjectTaskClass and ProjectTaskStatus enumeration to TestProjectTaskStatus enumeration.

    internal class TestProjectTaskClass : TaskBase
    {
        public new const string StatusSetName = "TestProjectClass";
    
        public TestProjectTaskClass(Session session)
            : base(session)
        {
        }
    
        public new static void UpdateDatabase(Session session)
        {
            UpdateDatabase<TestProjectTaskStatus>(session, StatusSetName);
        }
    
        protected override string GetStatusSetName()
        {
            return StatusSetName;
        }
    }
    
    public enum TestProjectTaskStatus
    {
        New,
        InProgress,
        Complete
    }

    Now that we moved all the common functionality tests to the TaskBaseTests class, we can modify the ProjectTaskTests class:

    [TestFixture]
    public class ProjectTaskTests : BaseXpoTest {
    public override void SetUp() {
        base.SetUp();
        ProjectTask.UpdateDatabase(Session);
        Session.CommitChanges();
    }
    
    [Test]
    public void StatusSet_HasValueAfterConstruction() {
        ProjectTask task = new ProjectTask(Session);
        Assert.IsNotNull(task.StatusSet);
        Assert.AreEqual(ProjectTask.StatusSetName, task.StatusSet.Name);
    }
    
    [Test]
    public void UpdateDatabase_CorrectStatusSequence() {
        TaskStatusSet statusSet = ProjectTask.FindTaskStatusSet(Session, ProjectTask.StatusSetName);
        Assert.IsNotNull(statusSet);
    
        TaskStatus currentStatus = statusSet.FirstStatus;
        Assert.AreEqual(ProjectTaskStatus.Draft.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatus.NotStarted.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatus.InProgress.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatus.Paused.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatus.Completed.ToString(), currentStatus.Name);
    
        Assert.AreEqual(null, currentStatus.NextStatus);
    }

    In the same way, we’d also create a test class for the EngineeringTask Class.

    In the previous post we’d written an EasyTest script to ensure that the ChangeStatus button is inactive for completed tasks and active for the incomplete. It turns out that sometimes the button’s state doesn’t correspond to the current task’s status, so we need an additional test:

    #DropDB XProjectEasyTest

    #Application XProjectWin

    #Application XProjectWeb

    *Action Navigation(Project Tasks)

    *Action Filter(All Tasks)

    *ProcessRecord

    Name = CompletedTask

    !ActionAvailable Change Status

    #IfDef XProjectWin

    *Action Close

    #EndIf

    *Action Navigation(Project Tasks)

    *ProcessRecord

    Name = DraftTask

    *ActionAvailable Change Status

    Now if we run it we’ll see that it fails:

    Application: XprojectWin Error in test: Action: 'Change Status' is available, line 13

    Let’s take a closer look at the ChangeTaskStatusController’s code. Set a breakpoint inside the OnActivated and View_CurrentObjectChanged methods. Then launch the application and navigate between various tasks. After that try to open a task which has the “Completed” status. We can see that when a Detail View is open the View_CurrentObjectChanged event doesn’t fire. Because of this the UpdateChangeStatusActionState method isn’t called and the button remains active. Bummer.

    To fix this, we’ll get rid of the CanChangeStatusController.UpdateChangeStatusActionState method  and instead we’ll use the Action.TargetObjectsCriteria property:

    public class ChangeTaskStatusController : ViewController {
        public ChangeTaskStatusController() {
                      …  
            changeTaskStatusAction.TargetObjectsCriteria = "CanChangeStatus";
           …
        }
    …
    }

    Now running the same test again shows that we have fixed the problem.

    The last thing we are going to do in this post is to code up the logic that says that if a task is marked as “Complete” then it’s progress must be 100%. To support this behaviour we’ll amend the TaskBase’s Progress property:

    public float Progress {
        get {
            if(this.Status == this.StatusSet.LastStatus)
                return 1;
    
            return TotalHours > 0 ? HoursSpent / TotalHours : 0;
        }
    }

    And there we go, we’re all done for this post. If you want to follow along at home then you can download the code from here. In the next post we’ll refactor our status system so that a user can choose a status from a list of possible values. In the meantime, happy XAFing!

  • Cloud Computing – It’s that New Old Thing

    Although the arguments over which was the first “real” computer rage on: is it the Zuse Z3, from Germany which was Turing complete but lacked conditional branching; is it the Colossus Mark 1, from the UK, which was programmable but not Turing complete; is it the ENIAC, from the US, which was both programmable and Turing complete but did not store any programmes onboard, or perhaps it was the Manchester Small-Scale Experimental Machine (or Manchester Baby as it was known), also from the UK, which was programmable, Turing complete and did store programmes onboard. Whichever is your personal choice, one thing is clear, they were eye-wateringly expensive.

    How expensive? Well, in 1956, the Farranti Pegasus sold for 50,000 GBP, that was when the average house in the UK would set you back a mere 2,500 GBP. To put that in context for you, that would mean that today the average computer would cost you just over 3,000,000 GBP! Of course, back then, there was no such thing as an “average” computer. Not every household had one, in fact not even every business had one. To demonstrate just how rare they were, Ferranti only sold 26 Pegasus 1 and 12 Pegasus 2 computers – of course, that still made it Ferranti’s most popular computer.

    So, if there were so few computers back then, how did computerisation work? Well, simply, you outsourced your computing entirely to something called a computer bureau. The bureau was there for companies that had neither the financial wherewithal to purchase their own computers, nor the expertise to program, operate and maintain such purchases. They worked by offering economies of scale; for a price, you could send your raw payroll data, for example, to a bureau and they would run the programs through the computer and return you the payroll output data.

    Of course the downside of this was fairly obvious: you had to transport your data to the bureau; their computers had to, reliably, run the calculations – this at a time when an engineer had to carry out an hour of preventative maintenance on the computer every morning before anyone went near it! Finally, the finished output data had to be returned to you. All this had to happen without a single error, if any part failed then the whole failed and you were left without your data. Bad news if it was payroll data and you couldn’t pay your workers. Of course, actions in mitigation had to be devised – what would you do if you didn’t get your payroll data back in time? Well you could just pay last month’s payroll, as a temporary measure, and make the required adjustments in the following month.

    Fast forward a couple of decades and the micro-processor comes on the scene. Computers plummet in price and every company who wants one can have one. They’re used for everything in the enterprise, from calculating the all important payroll, to the even more important managing the company’s lottery syndicate. Then something odd happens. Just as hardware is becoming simpler and cheaper we go and boik it all up by making the software more complex and more expensive. We add layer upon layer of abstraction; we have web servers talking to messaging servers talking to application servers talking to database servers and so on and so forth until the average company, not specialising in computing, throws up their hands and cries “Stop! Can’t I just outsource this into the cloud or something?”

    And with that we’ve come full circle. Now, for a price, you can send your raw payroll data, for example, to a SaaS application in the cloud and it will do the calculation and return you the payroll output data. Of course, you may wish to have your own programs for your enterprise, but you might not want to be saddled with the cost of maintenance of the application and storage servers. Well cloud computing can help you there too. Just purchase compute time and storage facilities as you require, and spin up more servers over peak times, and spin them down again when you’re done. Maximum flexibility minimum cost.

    Of course the downside of this was is fairly obvious: you had to transport your data to the bureau have to send your data to the cloud; their computers had have to, reliably, run the calculations – this at a time when an engineer had to carry out an hour of preventative maintenance on the computer every morning before anyone went near it! ISP can lose connectivity for days at a time. Finally, the finished output data had has to be returned to you. All this had has to happen without a single error, if any part failed fails then the whole failed fails and you were are left without your data. Bad news if it was is payroll data and you couldn’t can’t pay your workers. Of course, actions in mitigation had have to be devised – what would will you do if you didn’t can’t get your payroll data back in time? Well you could can just pay last month’s payroll, as a temporary measure, and make the required adjustments in the following month.

    Wait a minute… isn’t this all starting to sound rather familiar? If, like me, you think it is – then maybe it’s time to put a Beatles track on the turntable and shout viva la ‘60s!!

  • XAF – Project Management Application #8

    Welcome to part 8 of a series of blog posts illustrating the building of our project management application. In this post we’ll continue our work with Project statuses.

    Currently a user can change from one status to another without any consideration as to whether that change makes sense or not. This behaviour is not ideal, so we want to be able to specify which changes are valid and, as we already have the ProjectTaskStatuses enumeration, we’ll be using that to specify the order of the changes. The first thing we are going to do is to add the NextStatus property to the TaskStatus class:

    [Persistent]
    public TaskStatus NextStatus
    {
        get { return GetPropertyValue<TaskStatus>("NextStatus"); }
        protected set { SetPropertyValue<TaskStatus>("NextStatus", value); }
    }

    Then we’ll modify the constructor so that it takes the next valid Status as a parameter:

    public TaskStatus(Session session, TaskStatusSet owner, TaskStatus nextStatus, string name)
        : base(session)
    {
        Guard.ArgumentNotNull(owner, "owner");
        Guard.ArgumentNotNullOrEmpty(name, "name");
        Owner = owner;
        Name = name;
        NextStatus = nextStatus;
    }

    To reflect these changes, we’ll also have to change the ProjectTask’s UpdateDatebase method:

    public static void UpdateDatabase(Session session)
    {
        TaskStatusSet statusSet = FindTaskStatusSet(session);
        if (statusSet == null)
        {
            string[] statusNames = Enum.GetNames(typeof(ProjectTaskStatuses));
            TaskStatus[] statusList = new TaskStatus[statusNames.Length];
            statusSet = new TaskStatusSet(session, StatusSetName);
    
            TaskStatus nextStatus = null;
            for (int j = statusNames.Length - 1; j >= 0; j--)
            {
                string statusName = statusNames[j];
                statusList[j] = new TaskStatus(session, statusSet, nextStatus, statusName);
                nextStatus = statusList[j];
            }
            statusSet.FirstStatus = statusList[0];
            statusSet.LastStatus = statusList[statusList.Length - 1];
            statusSet.Save();
        }
    }

    And, being good programmers, we’ll provide a test for this functionality:

    [Test]
    public void UpdateDatabase_CorrectStatusSequence()
    {
        TaskStatusSet statusSet = ProjectTask.FindTaskStatusSet(Session);
        Assert.IsNotNull(statusSet);
    
        TaskStatus currentStatus = statusSet.FirstStatus;
        Assert.AreEqual(ProjectTaskStatuses.Draft.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatuses.NotStarted.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatuses.InProgress.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatuses.Paused.ToString(), currentStatus.Name);
    
        currentStatus = currentStatus.NextStatus;
        Assert.AreEqual(ProjectTaskStatuses.Completed.ToString(), currentStatus.Name);
    
        Assert.AreEqual(null, currentStatus.NextStatus);
    }

    We also need to write a functional test, in EasyTest, to ensure that users can’t set an incorrect status:

    #DropDB XProjectEasyTest
    
    #Application XProjectWin
    #Application XProjectWeb
    
    *Action Navigation(Project Tasks)
    *Action New
    
    *CheckFieldValues
     Status = Draft
     
    !FillForm
     Status = Completed

    When we run this test it fails, of course, as we’ve not yet specified how we are going to limit the user’s choice of Status. Now, there are two ways of going about this:

    1. We can limit the available Statuses in the combobox.
    2. We can make the combobox read only and apply the Status via an Action which would hold all the required logic for specifying the legal next status.

    I think we’ll favour the second option as the cleaner solution, so let’s mark the ProjectTask’s Status property with the Custom attribute, to make it read-only:

    [DataSourceProperty("StatusSet.Items"), Custom("AllowEdit", "false")]
    public TaskStatus Status {
        get { return GetPropertyValue<TaskStatus>("Status"); }
        protected set { SetPropertyValue<TaskStatus>("Status", value); }
    }

    This makes the Status field read-only on the UI, but we still have to implement the status changing functionality. To do this we’ll implement a new View Controller:

    public class ChangeTaskStatusController : ViewController
    {
        SimpleAction changeTaskStatusAction;
        public ChangeTaskStatusController()
        {
            changeTaskStatusAction = new SimpleAction(this, "ChangeStatus", PredefinedCategory.RecordEdit);
            changeTaskStatusAction.Execute += new SimpleActionExecuteEventHandler(action_Execute);
            changeTaskStatusAction.SelectionDependencyType = SelectionDependencyType.RequireSingleObject;
            TargetObjectType = typeof(ProjectTask);
        }
    private void action_Execute(object sender, SimpleActionExecuteEventArgs e)
    {
        ((ProjectTask)e.CurrentObject).Status = ((ProjectTask)e.CurrentObject).Status.NextStatus;
    }
    }

    As you can see from the constructor declaration, we didn’t use the Controller Designer. Instead we subscribe to the events and create an Action manually, adding the following functionality to our application:

    2

     1

    Well okay, this works but putting the business logic in the controller is not best practice as it’s hard to test and you get no reuse from your code, it also breaks the “separation of concerns rule”. So, to fix this, we’ll add the SetNextStatus method to the ProjectTask class:

    public void SetNextStatus()
    {
        if (this.Status == this.StatusSet.LastStatus)
        {
            throw new InvalidOperationException();
        }
        this.Status = this.Status.NextStatus;
    }

    And we’ll modify the controller’s execute event handler:

    private void action_Execute(object sender, SimpleActionExecuteEventArgs e)
    {
        ProjectTask currentTask = (ProjectTask)e.CurrentObject;
    
        currentTask.SetNextStatus();
        if (currentTask.Status == currentTask.StatusSet.LastStatus)
        {
            changeTaskStatusAction.Enabled.SetItemValue(string.Empty, false);
        }
    }

    And, just for good measure, we’ll write a test to ensure that an exception is thrown if a wrong status is set:

    [Test]
    public void IncorrectStatusSet()
    {
        ProjectTask task = new ProjectTask(Session);
    
        for (int i = 0; i < task.StatusSet.Items.Count - 1; i++)
        {
            task.SetNextStatus();
        }
    
        try
        {
            task.SetNextStatus();
            Assert.Fail();
        }
        catch (InvalidOperationException) { }
    }

    Since a task’s status is now changed by a dedicated method, we’ll make the ProjectTask’s Status field read-only, by making the setter protected, clearing all the old attributes and applying the Persistent attribute:

    [Persistent]
    public TaskStatus Status {
        get { return GetPropertyValue<TaskStatus>("Status"); }
        protected set { SetPropertyValue<TaskStatus>("Status", value); }
    }

    To ensure that the ChangeStatus Action is activated/deactivated correctly, we’ll write a functional test. The test requires a couple of test ProjectTasks in the test database. So, we’ll extend the Updater’s UpdateDatabaseAfterUpdateSchema method of the Xproject.Module project:

    public class Updater : ModuleUpdater {
       ...
        public override void UpdateDatabaseAfterUpdateSchema() {
            base.UpdateDatabaseAfterUpdateSchema();
    
            ProjectTask.UpdateDatabase(Session);
    
    #if EASYTEST
            ProjectTask task = new ProjectTask(Session);
            task.Name = "DraftTask";
            task.Save();
    
            task = new ProjectTask(Session);
            task.Name = "CompletedTask";
            task.SetNextStatus();
            task.SetNextStatus();
            task.SetNextStatus();
            task.SetNextStatus();
            task.Save();
    #endif
    
        }
    }

    And here’s the EasyTest test:

    #DropDB XProjectEasyTest
    
    #Application XProjectWin
    #Application XProjectWeb
    
    *Action Navigation(Project Tasks)
    
    *Action Filter(All Tasks)
    
    *SelectRecords
     Columns = Name
     Row = DraftTask
     
    *ActionAvailable Change Status
    
    *ClearSelection
    
    *SelectRecords
     Columns = Name
     Row = CompletedTask
     
    !ActionAvailable Change Status
    
    *ClearSelection
    
    *SelectRecords
     Columns = Name
     Row = DraftTask
     
    *ActionAvailable Change Status

    Umm… Which doesn’t work. If you run it, it fails with: “Error in test: Action: 'Change Status' is available, line 22”. It looks like, once disabled, the button stays disabled even for other project tasks. To correct this, we’ll modify the controller – update the action_Execute event handler and add another couple of event handler to update the Action’s status based on the currently selected task:

    public class ChangeTaskStatusController : ViewController {
        …
        protected override void OnActivated() {
            base.OnActivated();
    
            View.CurrentObjectChanged += new EventHandler(View_CurrentObjectChanged);
        }
        void View_CurrentObjectChanged(object sender, EventArgs e) {
            UpdateChangeStatusActionState();
        }
        private void UpdateChangeStatusActionState() {
            if(View.CurrentObject != null)
                changeTaskStatusAction.Enabled["ChangeTaskStatusController"] = ((ProjectTask)View.CurrentObject).CanChangeStatus;
        }
        private void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
            ProjectTask currentTask = (ProjectTask)e.CurrentObject;
            currentTask.SetNextStatus();
            UpdateChangeStatusActionState();
        }
    
    }

    For this updated Controller to work, we’ll add a new CanChangeStatus property to the ProjectTask class:

    [Browsable(false)]
    public bool CanChangeStatus
    {
        get { return this.Status != this.StatusSet.LastStatus; }
    }

    Now, if we run the test again we see that it passes – Phew!

    Well, I think we’ll leave it there for this post, the code is available if you want to play along at home. In the next post we’ll add a new kind of task with a new set of statuses, I hope you’ll join me for that. Until then, happy XAFing!

  • Has Google Been out Googled by Realtime Web Search?

    In this edition of Outside the DXperience we’re going to take a look at the Realtime web; find out what it is and how search differs between the Traditional Web and the Realtime Web; we’ll look at who the players are in realtime search and who amongst them, if any, has the upper hand; before looking at the question on everyone’s lips… has Google been out Googled?

    What is the Realtime Web?
    Well put simply, the Realtime Web is the here and now. Where the traditional web consists of pages from commercial sites, blog posts – both amateur and professional – news sites, weather sites and pages of information of every conceivable topic; they all have one thing in common: they are in the past tense. Someone has conceived them, written them, polished them and finally published them. By the time you read it the information contained in the pages of the Traditional Web is at least hours old.

    The Realtime Web, on the other hand, is the web of the here and now. Fuelled by social media tools like Facebook and Twitter that allow users to post their thoughts at any given time, on any given topic, the Realtime Web is a window on the life of the global citizenry. Posts on the Realtime Web are never really conceived but are more off the cuff remarks; the thinking out loud of any given, ordinary, person. These posts are certainly never polished but, instead, are a measure of the writer’s emotion at that time. Posts on the Realtime Web are usually what we call “soft” posts, containing opinion rather than fact; emotion rather than measured response. They are useful because they are what people think without the “white lie” that most of us use to survive in our day to day social interactions.

    Of course, saying posts on the Realtime web are “soft” is a generalisation, one that holds true in most circumstances certainly, but a generalisation nevertheless. One area where this is not true of course is in the area of citizen journalism. When some “Johnny on the spot” can post words and sometimes pictures too of events as they happen, in a way that no news corporation could do, unless they were very, very fortunate. One example of this has been the coverage of the Iranian election, where most, if not all, of the most interesting coverage has come from people inside the country getting the word out via Realtime Web tools like Twitter and YouTube. Another place where you see the Realtime Web excel compared to the Traditional Web is during a natural disaster, where news of earthquakes are commonly first heard on Twitter or FriendFeed before you hear about them on the mainstream news channels.

    What is the difference Between Traditional Web Search and Realtime Web Search?
    Traditional Web search works by having a “crawler” (a software agent) examine as many web pages as it can and index those pages in large server farms. Then, when someone makes a query, the index is examined and matching results are returned, based on an algorithm that each search engine company hopes will out perform the other. At the moment, the undisputed king of Traditional Web search is Google.

    Searching the Realtime web works differently. You don’t have to be a genius to realise that it would not be possible to store and index every piece of Realtime Web information as it is generated. Instead Realtime Web search relies on making API calls to social media service providers and have them provide a list of “what just happened now and in the recent past”. Twitter, for example, limit the results of any search to the previous seven days. 

    Who are the Players in RealTime Web Search?
    Until recently, the three major players in Realtime Web Search were Twitter, Facebook and Friendfeed. This, of course, has been whittled down to just two now that Friendfeed have been bought by Facebook. There are, of course, a number of smaller players, here are a few in their own words:

    Scoopler is a real-time search engine. We aggregate and organize content being shared on the internet as it happens, like eye-witness reports of breaking news, photos and videos from big events, and links to the hottest memes of the day. We do this by constantly indexing live updates from services including Twitter, Flickr, Digg, Delicious and more. When you search for a topic on Scoopler, we give you the most relevant results, updated in real-time.

    OneRiot crawls the links people share on Twitter, Digg and other social sharing services, then indexes the content on those pages in seconds. The end result is a search experience that allows users to find the freshest, most socially-relevant content from across the realtime web.

    TweetMeme is a service which aggregates all the popular links on twitter to determine which links are popular. TweetMeme is able to categorize these links into categories and subcategories, making it easy to filter out the noise to find what your interested in.

    Who has the Upper Hand?
    Of the two big players, Twitter and Facebook, it is hard to say who has the upper hand at the moment as both have their advantages, which I’ll summarise here for you now:

    Twitter

    It was Twitter who was first to market and that gives them the all important “first mover advantage”. There are millions of people who know Twitter search and reach for it as those in the realm of the Traditional Web reach for Google. Add to that the fact that Twitter has more experience, they’ve been in the game for a year now, learning what works and – more importantly perhaps – what doesn’t. This means that they have the knowledge to respond quickly to whatever Facebook can bring to the market place. But, to my mind, Twitter’s main advantage is that it is an open platform.

    Facebook

    Despite what you might have heard, size does matter and, in those terms, Facebook is way ahead. Facebook has over 250 million users. With those people to draw on, Facebook can provide a more accurate picture of what people are talking about at any given moment. You are also, statistically, more likely to have a friend on Facebook than on Twitter. Facebook’s search covers a wider range of media; as well as statuses Facebook search can deliver video and pictures as well which, by the way, can be filtered.

    Has Google Been Out Googled?
    Let’s be honest, Google was a fluke. Don’t look at me like that, it was! They started out with the idea of indexing the web and somewhere along the road they found that they could make money by placing adverts on the result pages of people’s searches. Fair play to the lads at Google, they exploited that discovery to the max and made a fortune doing it. What Google did was to shift the “value base” of content. Before Google came along (and to a certain extend before the web) the “value base” was with the content providers. “He who writes the words earns the money” as it were. However, Google shifted that “value base” to be “he who finds the content earns the money”.

    With the advent of the Realtime Web, that “value base” has shifted again to be “he who provides the tools earns the money”. We established above that no company – not even the mighty Google – can index all the Realtime Web content that is produced every second of every day, and we are going to become heavily dependant on the tool vendors to provide an API which we, or search engine companies, can access to gain results for a search query. Successful social media tool vendors can charge what they like to search engine companies like Google, Yahoo and Microsoft to have access to that API. This, I feel, is the most likely revenue stream for providers such as Twitter and if Google are not very careful here, they could very well find themselves out Googled.

  • XAF – Project Management Application #7

    This latest post in our series on building a project management application in XAF starts a sort of series within a series. Here we begin to look at adding the concept of statuses to our application. As this is a big topic, I don’t intend to bore you all to death by tackling it in one mega post. Instead, we’ll look at it over the next few posts. In this post however we’ll make a start. What we want to do here is to allow ProjectTasks to have a Status. Task Statuses should be configurable for different task types. Each status should also have an index, this will allow us to display them in a particular order. For example, an engineering task could have these statuses: Draft -> Planned -> Elaboration -> Development -> Documenting -> Completed. Also, some statuses should be considered “final”. This would mean that a whole task is assumed to be completed when such a status is specified. For a generic project task, the statuses could be: Draft -> Not Started -> In Progress -> Paused -> Completed.

    Before we get started on our statuses though, there’s a piece of work we have to do first and that is to implement the functionality that will allow us to categorize tasks into functional areas. Each functional area will have a name and will be owned by someone. So let’s implement that now by, firstly, creating the FunctionalArea entity:

    [DefaultClassOptions]
    [NavigationItem("Settings")]
    [DefaultProperty("Name")]
    public class FunctionalArea : BaseObject {
        public FunctionalArea(Session session) : base(session) { }
    
        public string Name {
            get { return GetPropertyValue<string>("Name"); }
            set { SetPropertyValue<string>("Name", value); }
        }
        public Employee Owner {
            get { return GetPropertyValue<Employee>("Owner"); }
            set { SetPropertyValue<Employee>("Owner", value); }
        }
    }

    Then we’ll add the FunctionalArea property to the ProjectTask:

    public FunctionalArea FunctionalArea {
        get { return GetPropertyValue<FunctionalArea>("FunctionalArea"); }
        set { SetPropertyValue<FunctionalArea>("FunctionalArea", value); }
    }

    This will make the following update to our winforms and webforms application GUIs:

    2

     1

    Now, with that done, let’s focus on our statuses. The easiest way to implement this would be as an enumeration:

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

    Of course doing this has an number of advantages: it’s quick to implement; the status property on the task is automatically initialised to the initial enumeration value and the last value can easily be considered to be the “final” status, as we spoke about above. However, implementing it this way does also have a number of disadvantages: we can’t use different status sets for different types or categories of tasks and a user could easily move from one status to another status when that status change is invalid; for example, from “draft” to “complete”.

    3

    4

    Because of this, we will implement the status as a business class:

    public class TaskStatus : BaseObject {
    
        public TaskStatus(Session session) : base(session) { }
        public TaskStatus(Session session, TaskStatusSet owner, string name)
            : base(session) {
            Guard.ArgumentNotNull(owner, "owner");
            Guard.ArgumentNotNullOrEmpty(name, "name");
            Owner = owner;
            Name = name;
        }
        public string Name {
            get { return GetPropertyValue<string>("Name"); }
            set { SetPropertyValue<string>("Name", value); }
        }        
        [Association]
        public TaskStatusSet Owner {
            get { return GetPropertyValue<TaskStatusSet>("Owner"); }
            set { SetPropertyValue<TaskStatusSet>("Owner", value); }
        }
    }

    We also spoke of the idea of a TaskStatus belonging to a TaskStatusSet, so let’s go ahead and implement that too:

    public class TaskStatusSet : BaseObject {
        
        public TaskStatusSet(Session session) : base(session) { }
        public TaskStatusSet(Session session, string name)
            : base(session) {
            Guard.ArgumentNotNullOrEmpty(name, "name");
            Name = name;
        }
    
        public string Name {
            get { return GetPropertyValue<string>("Name"); }
            set { SetPropertyValue<string>("Name", value); }
        }
        [Association, Aggregated]
        public XPCollection<TaskStatus> Items {
            get { return GetCollection<TaskStatus>("Items"); }
        }
    }

    Notice a one-to-many association between these two classes. The TaskStatusSet class has the aggregated Items collection holding the TaskStatus objects which form a particular TaskStatusSet. In the ProjectTask class we introduce the TaskStatusSet property. The Status property is marked with the DataSourceProperty attribute, so that only a status from the current status set can be selected:

    [DataSourceProperty("StatusSet.Items")]
    public TaskStatus Status {
        get { return GetPropertyValue<TaskStatus>("Status"); }
        set { SetPropertyValue<TaskStatus>("Status", value); }
    }
    public TaskStatusSet StatusSet {
        get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
        private set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
    }

    Now when we launch the app and create a new project task we’ll see that the Status and the StatusSet fields are empty and uninitialized since we haven’t created any objects of these types:

    6

    7

    Let’s fix this by extending the ProjectTask class so that it synchronizes its status list with the statuses from the database. In the future we’ll have different kinds of task, all inheriting from some form of task base class; when that time comes we’ll refactor this code into that base class, but for now, we’ll implement this code in the ProjectTask class:

    public static void UpdateDatabase(Session session) {
        TaskStatusSet statusSet = session.FindObject<TaskStatusSet>(new BinaryOperator("Name", StatusSetName));
    
        if(statusSet == null) {
            statusSet = new TaskStatusSet(session, StatusSetName);
            statusSet.Save();
        }
        List<string> currentStatuses = new List<string>(Enum.GetNames(typeof(ProjectTaskStatuses)));
    
        foreach(TaskStatus storedStatus in statusSet.Items) {
            if(currentStatuses.Contains(storedStatus.Name)){
                currentStatuses.Remove(storedStatus.Name);
            }
        }
        if(currentStatuses.Count != 0) {
            foreach(string statusName in currentStatuses) {
                session.Save(new TaskStatus(session, statusSet, statusName));
            }                
        }
    }
    public enum ProjectTaskStatuses {
        Draft,
        NotStarted,
        InProgress,
        Paused,
        Completed
    }

    Then we’ll add a call to this method in the Updater class:

    public class Updater : ModuleUpdater {
        public Updater(Session session, Version currentDBVersion) : base(session, currentDBVersion) { }
        public override void UpdateDatabaseAfterUpdateSchema() {
            base.UpdateDatabaseAfterUpdateSchema();
    
            //…
            ProjectTask.UpdateDatabase(Session);
        }
    }

    Which makes the following changes to the GUI:

    8

    9

    Well this works, sort of, but it’s not perfect is it? We’ll fix some of the issues now and leave others for future posts. When a ProjectTask is created the StatusSet field is initialized, let’s create a test for this:

    [TestFixture]
    public class ProjectTaskTests : BaseXpoTest {
        public override void SetUp() {
            base.SetUp();
            ProjectTask.UpdateDatabase(Session);
            Session.CommitChanges();
        }
        [Test]
        public void StatusSet_HasValueAfterConstruction() {
            ProjectTask task = new ProjectTask(Session);
            Assert.IsNotNull(task.StatusSet);
            Assert.AreEqual(ProjectTask.StatusSetName, task.StatusSet.Name);
        }
    }

    To make this test pass, we have to make the following change to he ProjectTask class:

    public override void AfterConstruction() {
        base.AfterConstruction();
    
        this.StatusSet = FindTaskStatusSet(Session);
    }
    private static TaskStatusSet FindTaskStatusSet(Session session) {
        return session.FindObject<TaskStatusSet>(new BinaryOperator("Name", StatusSetName));
    }

    Also, it makes no sense that the ProjectTask.Status field is visible in UI. To hide it we’ll apply a couple of attributes to it (VisibleInDetailView and VisibleInListView):

    [VisibleInDetailView(false), VisibleInListView(false)]
    public TaskStatusSet StatusSet {
        get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
        set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
    }

    The StatusSet field should only be initialized via the constructor, so we’ll mark the setter as private:

    [VisibleInDetailView(false), VisibleInListView(false), Persistent]
    public TaskStatusSet StatusSet {
        get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
        private set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
    }

    And, to be on the safe side, we’ll accompany this latest change with a test:

    [Test]
    public void StatusSet_HasValueAfterReloadFromDatabase() {
        ProjectTask task;
        TaskStatusSet taskStatusSet;
    
        using (UnitOfWork unitOfWork = new UnitOfWork()) {
            task = new ProjectTask(unitOfWork);
            taskStatusSet = task.StatusSet;
            Assert.IsNotNull(taskStatusSet);
            unitOfWork.CommitChanges();
        }
        using (UnitOfWork unitOfWork = new UnitOfWork()) {
            ProjectTask newTask = unitOfWork.GetObjectByKey<ProjectTask>(task.Oid);
            TaskStatusSet newTaskStatusSet = unitOfWork.GetObjectByKey<TaskStatusSet>(taskStatusSet.Oid);
            Assert.IsNotNull(newTaskStatusSet);
            Assert.AreEqual(newTaskStatusSet, newTask.StatusSet);
        }
    }

    When we run this test we see that XPO (the underlying ORM which XAF uses to persist our entities) has stopped persisting the StatusSet property’s value? Why? What happened there? Well it’s because we marked the setter as private, d’oh! Not to worry, we can easily fix this by applying the Persistent attribute:

    [VisibleInDetailView(false), VisibleInListView(false), Persistent]
    public TaskStatusSet StatusSet {
        get { return GetPropertyValue<TaskStatusSet>("StatusSet"); }
        private set { SetPropertyValue<TaskStatusSet>("StatusSet", value); }
    }

    The last issue that we are going to fix in this post is that the Status field doesn’t have a value assigned by default. To correct this, we need to extend the StatusSet class, to define the initial and final state. So that we can use the initial state as the default:

    public TaskStatus FirstStatus {
        get { return GetPropertyValue<TaskStatus>("FirstStatus"); }
        set { SetPropertyValue<TaskStatus>("FirstStatus", value); }
    }
    public TaskStatus LastStatus {
        get { return GetPropertyValue<TaskStatus>("LastStatus"); }
        set { SetPropertyValue<TaskStatus>("LastStatus", value); }
    }

    And to initialize these properties, we’ll extend the ProjectTask class’ UpdateDatabase method:

    public static void UpdateDatabase(Session session) {
        TaskStatusSet statusSet = session.FindObject<TaskStatusSet>(new BinaryOperator("Name", StatusSetName));
    
        if(statusSet == null) {
            statusSet = new TaskStatusSet(session, StatusSetName);
            statusSet.FirstStatus = new TaskStatus(session, statusSet, ProjectTaskStatuses.Draft.ToString());
            statusSet.LastStatus = new TaskStatus(session, statusSet, ProjectTaskStatuses.Completed.ToString());
            statusSet.Save();
        } //…

    }

    Finally, to set the initial Status when a ProjectTask is created, we need to update the AfterConstruction method as follows:

    public override void AfterConstruction() {
        base.AfterConstruction();
    
        this.StatusSet = FindTaskStatusSet(Session);
        this.Status = this.StatusSet.FirstStatus;
    }
    Phew, well that was quite a lot to fit into one blog post so I think we’ll end it there. In the coming posts we’re going to be resolving the following issues with statuses: currently, statuses are sorted by their name when displayed, while they should be logically ordered; a user can change any status to whatever other status he likes (e.g. Completed -> NotStarted) and lastly, the “New” and “Clear” actions shouldn’t be available in the lookup when changing the status. As always, you can download the source code to this project. Until next time, happy XAFing!
More Posts Next page »
Copyright © 1998-2010 Developer Express Inc.
ALL RIGHTS RESERVED