Blogs

Gary's Blog

March 2011 - Posts

  • XAF–Domain Components How-to Implement an Address in a Business Entity

         

    This post may be outdated. For the latest Domain Components concepts and examples refer to the current online documentation.

    Addresses are funny things aren’t they? If you own the the Address class, and you are writing it for your own application then they are easy things to handle. Each address object is a composition of a number of string types containing the information you require for your application. Simple and straight forward. Of course, as soon as you try to write a reusable interface library, things start to get a little more complicated, so let’s take a look at how we did it.

    First, let’s start with  an IBaseAddress component:

    public interface IBaseAddress
    {
        string DisplayName { get; set; }
    }

    Having this base class for addresses we can now go on to define an IAddressable interface, which entities that require to have one, or more, addresses can use – things like Contacts, Orders, Shipping Notes etc. We can define our IAddressable interface like so:

    [DomainComponent]
    public interface IAddressable
    {
        [DevExpress.ExpressApp.DC.Aggregated]
        [VisibleInListView(false)]
        IBaseAddress PrimaryAddress { get; set; }
    
        [CreateInstance]
        IBaseAddress CreateAddress();
    }

    And we can use it, wherever we need to, like this:

    public interface ICRMContact : IContact, IAddressable, ...
    public interface ICRMAccount : IAccount, IAddressable, ...
    public interface ICRMLead : ILead, IAddressable, ...

    In this interface, there are two significant parts: the PrimaryAddress member, which is aggregated, and it brings in the minimum information of an addresses – the primary address.
    The second significant part is the CreateAddress method that is marked with the CreateInstance attribute. This method is used as a factory method to create an entity that is registered for the IBaseAddress domain component. This method is autogenerated and will work when one entity in the application implements the IBaseAddress interface. In other cases, you need to implement it manually and you’ll have to specify which entity should be created, in this method, when it is called:

    [DomainLogic(typeof(IAddressable))] 
    public class AddressableLogic { 
        public static void AfterConstruction(IAddressable addressable) { 
            if(addressable.PrimaryAddress == null) { 
                addressable.PrimaryAddress = addressable.CreateAddress(); 
            } 
    }

    Now our implementation is far from perfect. Using a string makes it hard to process the address by address part, to filter by a city for example. It is also impossible to guarantee any strict form of address format, which your application may need to do. Providing default lists, say of cities or of US states, is also difficult to do.

    So, let’s improve things by defining a generic address component in our library, this component can be substituted with your own implementation should you need something more specific.

    Our IGenericAddress component will look like this:

    [DomainComponent]
    public interface IGenericAddress : IBaseAddress
    {
        string Street1 { get; set; }
        string Street2 { get; set; }
        string City { get; set; }
        string State { get; set; }
        string Zip { get; set; }
        string Country { get; set; }
    }

    Here you will notice that the single string DisplayName has been broken down into several string members, and is constructed from these parts when any of them is modified:

    [DomainLogic(typeof(IGenericAddress))] 
    public class GenericAddressLogic { 
    private static void UpdateDisplayName(IGenericAddress address) { 
    address.DisplayName = string.Format("{0} {1} {2} {3} {4} {5}", address.Street2, address.Street1, address.City, address.State, 
    address.Zip, address.Country); 
    } 
    public static void AfterChange_Street1(IGenericAddress address) { 
    UpdateDisplayName(address); 
    } 
    public static void AfterChange_City(IGenericAddress address) { 
    UpdateDisplayName(address); 
    } 
    ...

    With this approach, it is possible for you to introduce any other Address implementation into any library component that contains the IBaseAddress member.

    On the Order UI in our XCRM demo application, the address UI looks like this:

    XCRM Order Address

    Well that’s all for this post, until next time, happy XAF’ing! Smile

  • XAF - Domain Components How-to: Implement Notes Functionality in a Business Entity

         

    This post may be outdated. For the latest Domain Components concepts and examples refer to the current online documentation.

    Notes, every business needs them. Every person in the company who has contact with a customer has to be able to record that fact in a CRM system. The last thing you want to do is to be a salesmen phoning a customer, in the hope of getting a large order, only to discover that the last contact the customer had with your company was to complain bitterly about the shoddy service he just received. If there was a note in the system to that fact, then you could either phone once it’s been resolved, or you could change your sales tactic.

    Of course, saying you need notes is one thing, actually implementing it is quite another, especially if your class hierarchy already exists. You have to introduce the Note type, you have to create a member on the business objects to hold that new type, and most painful of all, you have to make room on the UI for the new note type. All and all you are in a world of pain. Now XAF can help you with the UI stuff, it will scaffold an appropriate UI for your types, but that still doesn’t save you from the pain of all the changes you have to make to your types.

    Well enter Domain Components to save the day! The Domain Component technology will do all that hard work for you. First, let’s declare an INote interface:

    [DomainComponent]
    [XafDefaultProperty("Title")]
    public interface INote {
        [RuleRequiredField(NoteValidationRules.NoteTitleIsRequired, DefaultContexts.Save)]
        [FieldSize(255)]
        string Title { get; set; }
    
        [FieldSize(4000)]
        string Description { get; set; }
        
        IPersistentFileData Attachment { get; set; }
    }

    As you can see it has a mandatory title property, so that you can name your note something meaningful, a description field, big enough to hold a reasonable sized note, and a field to hold an attachment, in case you wish to append a file to the note. Oh, and here’s the NoteValidationRules class that we’ve used in the RuleRequiredField:

    public class NoteValidationRules {
        public const string NoteTitleIsRequired = "NoteTitleIsRequired";
    }

    This pattern let’s us extend this behaviour, if we need to, at a later date.

    So far so good, but it’s not really useful to have one note attached to a business class, what we really need is a notes collection, so let’s go ahead and define something to hold a collection of notes:

    [DomainComponent]
    public interface INotes {
        [DevExpress.ExpressApp.DC.Aggregated]
        IList<INote> Notes { get; }
    }

    This notes interface that we’ve just created is a pluggable part of the notes extension and now we can use it wherever we wish:

    public interface ICRMActivity : IActivity, INotes ...
    public interface ICRMContact : IContact, INotes, ...
    public interface ICRMInvoice : IInvoice, INotes, ...

    And so on and so forth.

    So, as you can see, Domain Components, have saved us an awful lot of work when it comes to adding this notes functionality. After it has been added to the Contact, the UI looks like this:

    Notes on the Contact UI

    And it looks like this, when added to the Invoice:

    Notes on the Invoice UI

    As you can see, in this post, I have not covered testing, neither unit testing, with something like NUnit, nor functional testing with EasyTest. I didn’t add the testing stuff in as I thought it might complicate matters, but if you feel that it wouldn’t and you’d like to see testing covered in this series, then let me know in the comments and I’ll add it to the next one.

    Well, that’s all for this post, until next time, happy XAF’ing! Smile

  • Data Layer – 11.1 Sneak Peek – Custom Functions Part 2

         

    In a previous blog post I showed you how to use custom functions everywhere you could use a CriteriaOperator. In this post, I’m going to show you how to use custom functions in LINQ to XPO.

    There are two interfaces that support this functionality, they are:

    1. ICustomFunctionOperatorQueryable – which provides information about the method that will be associated with the custom function.
    2. ICustomCriteriaOperatorQueryable – which provides information about the method that will be associated with custom criteria, and implements the behaviour to convert a LINQ expression to a CriteriaOperator.

    Right, let’s write some code! To start with let’s open the project, from the previous post, in Visual Studio 2008 or greater, set target framework to 3.5 or greater and add a reference to the DevExpress.Xpo.v11.1.Linq assembly.

    Next, in the MyConcatFunction class, add the implementation of ICustomFunctionOperatorQueryable

    using DevExpress.Xpo.Helpers;
    //...
    public class MyConcatFunction : ICustomFunctionOperatorBrowsable, 
        ICustomFunctionOperatorFormattable, 
        ICustomFunctionOperatorQueryable 
    {
        //The method name must be the same as the function name,
        //but it doesn't have to be in the same class
        public static string MyConcat(string string0, string string1, string string2, string string3) {
            return string.Concat(string0, string1, string2, string3);
        }
        #region ICustomFunctionOperatorQueryable Members
         
        public System.Reflection.MethodInfo GetMethodInfo() {
            return typeof(MyConcatFunction).GetMethod("MyConcat");
        }
        #endregion
    }

    Finally we will drop another GridControl (imaginatively called GridControl2 Smile) onto the form and add the following code to the form constructor:

    var linqResult = from p in new XPQuery<Person>(Session.DefaultSession)
                     select new
                     {
                         p.FirstName,
                         p.LastName,
                         NameLinqToXpo =
                             MyConcatFunction.MyConcat(p.FirstName,
                             " ", 
                             p.LastName, 
                             " (Linq To Xpo)")
                     };
    gridControl2.DataSource = linqResult.ToList();

    Having done that, running the application will give us this result:

    Example Form showing custome functions in LINQ to XPO

    That’s all for this short post, until next time, happy coding! Smile

  • Developer Express at DevWeek in the UK

         

    IMG_2104DevWeek is the UK’s largest developer focussed conference with some of the most respected speakers in the world in attendance. Running from the 14th-18 of March the conference combines pre-conference and post-conference workshops with days of sessions in between. This year’s sessions covered topics like: MVC, Azure, C# deep dives and Sharepoint. I’m sure you’ll agree there was enough there for every technical taste.

    Of course Developer Express was there in full force. We were exhibiting at the conference, which enabled me to meet a large number of DevExpress customers as well as those of you who are thinking about becoming customers. I had some great conversations at the booth on a number of great topics, including the future of WPF and Silverlight, so nothing too controversial then Winking smile. I also demonstrated a lot of great DevExpress technology to attendees, including some of our new Silverlight stuff as well as my own first love, XAF.

    Not only were we exhibiting but I was lucky enough to be asked to present a session on NoSQL this year. The session was standing room only as I covered the different types of NoSQL databases, before covering a range of scenarios and recommending a particular type of NoSQL database for each. I finished up with an deep dive into a use case showing how a company pulled themselves out of a situation, which could have easily cost the them their existence, with the use of a NoSQL database. The session itself was standing room only and those in attendance asked some really thought provoking questions and sparked some great conversations, some of which were carried on back at the booth, when some of the attendees sought me out for a follow up chat.

    Making the most of our time in London, we finished up with a great mixer event. I had a lot of fun and met a lot of interesting people and had some great conversations. Rachel has more details of this event on her blog, so pop across there and check out what a great time everyone had.

    All and all I think DevWeek is one of the best conferences in the UK and I look forward to returning next year with the DevExpress team.

  • Data Layer – 11.1 Sneak Peek – Custom Functions Part 1

         

    One of the exciting changes that we are looking forward to shipping in 11.1, is the global registration of custom functions. Once registered, a custom function can be used where ever CriteriaOperators are processed, as well as being available in all Expression Editors.

    So how does it work? Simply, a custom function is a class which implements several interfaces:

    1. ICustomFunctionOperator – is responsible for base functionality such as function name, result type and client implementation of the function.
    2. ICustomFunctionOperatorFormattable – is responsible for the formatting (implementation) of the function on the server (database) side.
    3. ICustomFunctionOperatorBrowsable - provides additional information for Expression Editors such as function category, function description, argument count etc.

    ICustomFunctionOperatorFormattable and ICustomFunctionOperatorBrowsable inherit from ICustomFunctionOperator. So, if you implement either of first two interfaces, there is no need to implement the last.

    Knowing this, let’s write our own custom function to handle concatenation, let’s call it “MyConcat”

    using DevExpress.Data.Filtering;
    using DevExpress.Xpo.DB;
    using System.Resources;
    using CustomFunctionsExample.Properties;
    
    public class MyConcatFunction : ICustomFunctionOperatorBrowsable, 
    ICustomFunctionOperatorFormattable { const string FunctionName = "MyConcat"; #region ICustomFunctionOperatorBrowsable Members static readonly MyConcatFunction instance = new MyConcatFunction(); public static void Register() { CriteriaOperator.RegisterCustomFunction(instance); } public static bool Unregister() { return CriteriaOperator.UnregisterCustomFunction(instance); } public FunctionCategory Category { get { return FunctionCategory.Text; } } public string Description { get { return Resources.MyConcatDescription; } } public bool IsValidOperandCount(int count) { // At least two operands must be specified. return count > 1; } public bool IsValidOperandType(int operandIndex, int operandCount, Type type) { // All operands should be strings. return type == typeof(string); } public int MaxOperandCount { //Accepts an infinite number of operands. get { return -1; } } public int MinOperandCount { // At least two operands must be specified. get { return 2; } } #endregion #region ICustomFunctionOperator Members // Evaluates the function on the client. public object Evaluate(params object[] operands) { StringBuilder result = new StringBuilder(); foreach(string operand in operands) { result.Append(operand); } return result.ToString(); } public string Name { get { return FunctionName; } } public Type ResultType(params Type[] operands) { foreach(Type operand in operands) { if(operand != typeof(string)) return typeof(object); } return typeof(string); } #endregion #region ICustomFunctionOperatorFormattable Members // The function's expression to be evaluated on the server. public string Format(Type providerType, params string[] operands) { // This example implements the function for MS Access databases only. if(providerType == typeof(AccessConnectionProvider)) { StringBuilder result = new StringBuilder(); result.Append("("); for(int i = 0; i < operands.Length; i++) { if(i > 0) result.Append(" + "); result.AppendFormat("({0})", operands[i]); } result.Append(")"); return result.ToString(); } throw new NotSupportedException(providerType.FullName); } }

    In the code above the Register and Unregister methods simplify the global registration process of our custom function whilst the Description property of the MyConcatFunction class returns a description for our custom function, which we fetch from our resources. This example implements formatting for MS Access only.

    Having done this, let’s write a Person persistent class:

    Next let's write the persistent class Person.

    using DevExpress.Xpo;
    public class Person: XPBaseObject {
        int oid;
        [Key(true)]
        public int Oid {
            get { return oid; }
            set { SetPropertyValue<int>("Oid", ref oid, value); }
        }
        string firstName;
        public string FirstName {
            get { return firstName; }
            set { SetPropertyValue<string>("FirstName", ref firstName, value); }
        }
        string lastName;
        public string LastName {
            get { return lastName; }
            set { SetPropertyValue<string>("LastName", ref lastName, value); }
        }
        //Using our custom function in PersistentAlias
        [PersistentAlias("MyConcat(FirstName, ' ', LastName, ' 1')")]
        public string Name {
            get { return (string)EvaluateAlias("Name"); }
        }
        public Person(Session session) : base(session) { }
    }

    As you can see, in the Name property, we are using our new custom function in the same way as we would use a built-in function.

    Next, we’ll add function registration into the Main method of the Program class:

    [STAThread]
    static void Main() {
        MyConcatFunction.Register();
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    Now, let’s create a new form and drop the GridControl(gridControl1), Session(session1)and XPView(xpView1) components onto it, then set the Session property of the xpView1 to session1. After that we will add properties (Name - Property accordingly) to xpView1 as shown below:

    • FirstName - [FirstName]
    • LastName - [LastName]
    • NamePersistentAlias - [Name]
    • NameViewProperty - MyConcat([FirstName], ' ', [LastName], ' 2')

    ViewProperty Collection Editor

    Here we use the MyConcat function in the ViewProperty.

    Now let’s select xpView1 as a data source of the gridControl1 component, run the designer for gridControl1 and add a column with following properties:

    • Caption = Name (Unbound Expression)
    • FieldName = name of the column
    • ShowUnboundExpressionMenu = True
    • UnboundExpression = [MyConcat]([FirstName], ' ', [LastName], ' 3')
    • UnboundType = String

    Finally add the following code into the form constructor:

    string[][] names = new string[][] {
        new string[] { "John", "Smith" },
        new string[] { "Fred", "Scott" },
        new string[] { "James", "King" }
    };
    
    using(UnitOfWork uow = new UnitOfWork()) {
        XPCollection<Person> persons = new XPCollection<Person>(uow);
        uow.Delete(persons);
        uow.CommitChanges();
    }
    
    using(UnitOfWork uow = new UnitOfWork()) {
        for(int i = 0; i < names.Length; i++) {
            Person person = new Person(uow);
            person.FirstName = names[i][0];
            person.LastName = names[i][1];
        }
        uow.CommitChanges();
    }

    Then run the application to see the results:

    Sample App

    Now that the application is running, you can select the function, from the list within the Unbound Expression Editor. To invoke this editor, click the corresponding item in the context menu for the "Name (UnboundExpression)" column.

    Expression Editor

    As you can see, our new function is listed!

    Well that’s all for this post, ‘til next time happy coding! Smile

  • XAF – UI Customization Webinar 10:00 PST / 18:00 GMT

         

    image

    Just a reminder that I’ll be delivering a webinar tonight on the subject of UI customizations in XAF, you can register for the webinar here. The webinar will be held at 1000 PST / 1800 GMT so I hope to see all you XAF’ers there. Smile

    You can get a full listing of the upcoming webinars here. You can also review all the completed webinars at your leisure.

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.