Search This Blog

Showing posts with label Reusable Modules. Show all posts
Showing posts with label Reusable Modules. Show all posts

Wednesday, February 14, 2018

Learning through reading the source code

I will start this post with the new Visual Studio 2017 function, which I hope will be helpful for XAF developers too. I have both RTM and Preview versions installed, but I am mostly using the latter one for testing. The new function I spotted in their change logs is called "View decompiled source definitions instead of metadata (C#)".

As you can guess from its name, it changes the very often used "Go To Definition" command (F12) so that it displays the actual code of a C# type or member who's source code is unavailable - very useful and convenient, IMHO. It is decompiled using ILSpy, the tool I have already been using for years as default program association for opening *.dll files -  partially the reason of why this news got my attention. This experimental feature is new in Visual Studio 2017 version 15.6 preview 2+ and it must be turned on explicitly using the Tools > Options  >  Text Editor > C# > Advanced > Enable navigation to decompiled sources menu item. 


Thursday, July 14, 2016

How to reuse XAF Views and other standard module functionality in non-XAF apps

And one more update of the popular support topic, but quite advanced, though. Anyway, here we go:


Take special note that this is a specific and non-standard use-case scenario, which is not intentionally supported and tested by us internally. By default, many application modules rely on the XAF infrastructure and cannot be used outside it without special initialization or boilerplate code, which is usually created in the default XAF project templates created by the XAF Solution Wizard (see the YourSolutionName.Win/Program.xx orYourSolutionName.Web/Global.asax.xx files for more details). At the moment, most functionality of UI modules available to end-users is supposed to work in the client XAF WinForms, ASP.NET Web Forms and Mobile apps. For instance, you cannot have an arbitrary XAF DetailView embedded into your non-XAF ASP.NET MVC or WinForms app without much preparation (see the example below for more details) or at all with all the standard XAF functionality included.


Even though there are also non-visual XAF core components that can be reused in non-XAF apps with less effort (e.g., our security, audit and validation engines), they still require implementing custom-tailored solutions and a special initialization manually in your non-XAF app. Finally, since we do not test such scenarios internally, we do not guarantee normal operation of all XAF components in non-XAF apps. Depending on the module and target use scenario, there may be nuances and you must carefully test these scenarios or may probably be required to write additional code to get it working according to your business needs. For instance, since many standard modules (security, validation, reports, etc.) depend on the XafApplication.LoggedOn event or the activation of controllers for the main application window to perform their initialization, you may need to replicate the same circumstances in your non-XAF app if you want to make use of dependent functions. Also, if you're developing a Windows Forms app, it must be single-threaded (STA) by design, so you cannot easily invoke the standard XAF WinForms functionality in threads other than the main one. 


On the other hand, it is important to note that in a well-designed and structured application, it is also not difficult to separate and reuse the ORM data model or business classes, which are normally not tied to the visual or UI parts. Very often, it is the best practice to have a single common or shared class library for your persistent classes, which may not have any XAF dependencies. This way, to use persistent objects in non-XAF applications and services, it is sufficient to add a reference to this shared assembly and then follow  your ORM documentation (e.g., XPO) to learn more on how to set up database connection, create, query, delete persistent objects.

Monday, July 20, 2015

A third-party Twitter Bootstrap Add-on for XAF Web UI

I wanted to share a recent announcement of the open source project from our long-time customers from Kazakhstan:


Talgat Shalabayev, the tech lead of the team providing this custom XAF module said in our LinkedIn group (if you are not yet a member of this forum/group where XAF users share their experience, be sure to join it as interesting things are happening there recently):

"I am glad to inform that we have released XAF Bootstrap addon. It implements Twitter Bootstrap functionality for Web module. You can test and use it now. Project is licensed under open source.
Addon is used in a several our projects in production, but it was tested only with our production needs, not all XAF configuration options, and released "as is". For example, it works for now only with XPO (EF not supports for now), .Net 4.5 and there may be more configuration-specific issues. We plan gradually increase supported features, but we hope that interested community members will join to testing or developing this addon - it will speed up addon's way to production ready state."

You can also see the video guys posted at https://www.youtube.com/watch?v=mIZcLswlswM for more understanding of what this module provides and how to add it into an existing XAF Web project.

My own experience

I decided to test this module with our XCRM demo and first downloaded it from GitHub (they now provide compatible versions for both v14.2 and v15.1 of DevExpress assemblies). 
Since the module currently supports only XPO and .NET 4.5, the demo app required some adjustments to be usable with this module (because it is EF-based). Once I fixed this (by registering several ObjectSpaceProviders for XPO and EF within a single app and modifying the ModuleUpdater class code accordingly), the resultant Web app looked quite well (except for some visual glitches in the menu and other side effects):

Thursday, February 26, 2015

Check out an example demonstrating a possible way of integrating Snap reports in an XAF WinForms application

The example integration shows how to store DevExpress Snap reports layout in business objects and show a list of these objects, as this is done in the built-in Reports module. The main tool for working with these reports is a custom Snap designer form. Underlying business objects can be configured using custom buttons from this form. It is also possible to show a read-only preview form using corresponding actions.

You can download the actual test project and research the example implementation from here: 

Wednesday, January 21, 2015

Simplifying integration of custom controls bound to data from XAF application database - YOUR FEEDBACK IS NEEDED

Scenario


We are researching options to make it easier for our users to achieve the subject in the next version. One of the popular scenarios we were aware of from our users was integration of custom controls, often created in Visual Studio using the standard WinForms or ASP.NET WebForms approaches, and primarily designed for presenting data from the XAF application database in some very custom manner so that built-in XAF Property Editors  or List Editors were not good for this. For instance, you might want to show a list of records in a fancy grid with cards, image gallery or other controls or modes not integrated by default.




Current solutions and problems

XAF is very extensible framework and offers many ways of integrating custom or third party controls. We noticed that while doing so, feeding these custom controls with data from the application database was often a challenge for our users. This binding is done automatically for built-in forms and data editors, but requires writing some code that will read data via the IObjectSpace.GetObjects<T> method (to respect data security filters) and also listen to the IObjectSpace.Reloaded and other events to handle data updates. Finally, designing a data bound user control in Visual Studio often implies a direct connection to the database or using specialized data sources (e.g., to generate a list of grid columns or pivot fields), which do this for you. The latter forces you to think about obtaining an application connection string at design time, while this dynamic part is changed later in the most cases. Add to this different approaches for Entity Framework and XPO and you will get the whole picture.

New solutions (not yet built-in!)

Briefly, we decided to simplify this scenario. Here are two videos that shows one possible option:

ASP.NET:  http://www.screencast.com/t/OHhcHD9vy

WinForms: http://www.screencast.com/t/8M8K4eskkYO9

How it works?


In short, the proposed flow is as follows:

Tuesday, September 9, 2014

Example of a custom PropertyEditor based on the ASPxColorEdit control

I wanted to share an example of a custom XAF Property Editor for viewing and editing System.Drawing.Color properties on the Web. This is what it looks like in the UI:


This custom PropertyEditor uses the built-in ASPxColorEdit control from our ASP.NET Suite and provides a bit better UX than the default ASPxColorPropertyEditor:

Sunday, August 17, 2014

Are you ready for some serious Xafari?

I am a bit late with this announcement, and you might have already been aware of the XAFARI.NET platform from our news feeds in developer groups, but here we go. 

If you do not recall my introduction blog post "Meet XAFARI - a business platform from Galaktika Corp", please refresh your memory first to learn more on this powerful business platform, which is built on top of eXpressApp Framework (XAF) to provide more reusable modules and components for various verticals.

The main reason for this post is that XAFARI got an English version of their website (it was only in Russian previously), which means that more XAFers could benefit from the numerous features this commercial third-party platform provides:



To learn more on pricing, features and services, including but not limited to access to the source code, online documentation, priority support, etc), check out http://galaktikasoft.com/xafari/buy (of course, free trial included):

Tuesday, October 8, 2013

Managing data from the associated details collection via a checked list box control

Here is one more contribution from the XAF community - a new Property Editor developed by the international company Galaktika as part of their XAFARI business platform (learn more about these guys in my introductory post). This editor can replace the built-in ListPropertyEditor in certain business scenarios, especially if you want to save more space on your data form.

Here are some screenshots for you to better understand the functionality I am talking about:


Configuration in the Model Editor


Result in the WinForms UI

You can download this reusable solution from http://xafari.ru/download (13.1.7 build is available!)

Thursday, August 22, 2013

Wizards - XAFARI way

I wanted to inform you about another bright contribution from the XAF community - a new Wizards module developed by the international company Galaktika as part of their XAFARI business platform (learn more about these guys in my introductory post).

Here are some screenshots for you to better understand the functionality I am talking about:

Configuration in the Model Editor

 Result in the Web UI

Result in the WinForms UI

Monday, August 19, 2013

XAFARI 13.1 is available

I am back from a short vacation, and I would like to inform you that XAFARI - a business platform from Galaktika Corp has recently been updated to the version 13.1.5 of XAF: http://xafari.ru/download

You can learn more on what is new in XAFARI 13.1.5 from http://xafari.ru/news/reliz-xafari-13-1-5

If you like the new and existing XAFARI features, do not hesitate to like their page on FB:

P.S.
I should remind that the English version of the XAFARI web site is still under construction (I believe the more users feedback this XAF extension gets, the sooner the site will be finished), but the built-in Google Chrome translation from Russian to English will help you understand most of the info - at least that works for me;-)


Friday, June 7, 2013

Advanced (JQuery UI-based) Tooltips for Detail View Items on the Web

I would like to announce another community contribution from an active XAF customer (Thanks Krzysztof!):

https://github.com/KrzysztofKielce/xaf-tooltips

What it looks like?
Here is what it looks like in an XAF Web app:


Main features 
Below is the list of supported features set by the author:

- is easy defined
- uses jQuery and jQuery-ui tooltip
- supports html tags
- has no character number limit (as in standard IE browser dialog)

Example of use:

Set an attribute on your entity property:

    [Tooltip("This is a sample tooltip from an <br/><br/>(some BR tags)<br/><br/><br/><br/>attribute")]

Or set a text in Model.DesignedDiffs.xafml (in a detail view layout item).

Learn how it is implemented!
The author also carefully documented the signification source files that form this solution and you can see this info in the "Significant files" section on the contribution page.

For you to learn how this works from the XAF perspective, let me make a short dissection of his solution:

1.  As you know, XAF allows you to customize built-in UI elements or replace them with your own versions. The Views Layout on the Web is powered by the WebLayoutManager, which renders Property Editors and  other View Items defined in the Application Model in the UI using web controls and ITemplate objects. Here a custom ITemplate was created for the layout item by inheriting from the built-in LayoutItemTemplate class (see more...).

2. To tell XAF to use this new custom layout item template, the WebLayoutController class was created (see more...): it just set a property the WebLayoutManager.
Find more details on this in the DevExpress Support Center.

3. Since the solution was originally created for version 12.1, the author implemented his own ToolTipAttribute and Application Model extenders (see more...). This is is NOT required in the latest XAF versions, because XAF provides the built-in ToolTipAttribute and corresponding properties in the Application Model. Just in case you would want to define a custom attribute for decoration of business class properties in code or configuring it via the Application Model, you can see how the XAF's attribute is implemented (see at "C:\Program Files (x86)\DevExpress\DXperience 12.2\Sources\DevExpress.ExpressApp\DevExpress.ExpressApp\DevExpress.Persistent.Base\Attributes.cs" ):

4. The latter part is important, but does not require much commenting as it is standard for ASP.NET development:
  • SampleProject.Web\Default.aspx, DefaultVertical.aspx, Dialog.aspx - modified head section
  • SampleProject.Web\scripts\jquery-ui.min.js (new)
  • SampleProject.Web\scripts\jquery.min.js (new)
  • SampleProject.Web\style.css (new)
I just want to say that in the latest XAF versions you will have to apply the aforementioned changes after adding a custom ASP.NET templates per these instructions from the XAF documentation.

Hopefully, this bright contribution will be recognized by other community members and added into the eXpand Framework (http://www.expandframework.com/) by default.

Happy XAFing and contributing, as always!









Wednesday, June 5, 2013

Discussing UI customization in XAF by example of customizing controls associated with Actions on the Web

One of the ways to access and customize controls associated with Actions on the Web is to inherit from the ProcessActionContainerHolderController class. It represents a base class from which Controllers (there is a number of standard descendants) customizing Action Container controls are derived.

Example
For instance, imagine that we want to enable scrolling of large menu items in our Web application and want to use the following feature of our DevExpress ASP.NET menu control: ASP.NET Menu Scrolling - today I had a real customer wanting to perform this customization.

So, you can consider using the following example code in version 12.2+:

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Web.SystemModule;
using DevExpress.ExpressApp.Web.Templates.ActionContainers;
using DevExpress.ExpressApp.Web.Templates.ActionContainers.Menu;
using DevExpress.Web.ASPxMenu;

namespace MainDemo.Module.Web.Controllers {
    // This is a demo controller that builds an Action with a lot of items in the UI.
    public class Class1 : ViewController {
        public Class1() {
            SingleChoiceAction dropDownMenuAction = new SingleChoiceAction(this, "Test", DevExpress.Persistent.Base.PredefinedCategory.View);
            dropDownMenuAction.ItemType = SingleChoiceActionItemType.ItemIsOperation;
            for (int i = 0; i < 100; i++) {
                dropDownMenuAction.Items.Add(new ChoiceActionItem("Test" + i.ToString(), i));
            }
        }
    }
    // This is our descendant of the standard controller to access and customize controls of a required Action.
    public class EnableScrollingMenuItemsController : ProcessActionContainerHolderController {
        protected override void OnActionContainerHolderActionItemCreated(WebActionBaseItem item) {
            base.OnActionContainerHolderActionItemCreated(item);
            MenuActionItemBase menuItem = item as MenuActionItemBase;
            if (menuItem != null && menuItem.MenuItem.Menu is ASPxMenu && menuItem.Action.Id == "Test") {
                // Accessing the ASPxMenu control and customizing it according to the DevExpress ASP.NET documentation.
                ((ASPxMenu)menuItem.MenuItem.Menu).EnableSubMenuScrolling = true;
            }
        }
    }
}

That's it and we can now run our test app (I used our MainDemo) to see the result:


What we might have learned here?

  • Controllers can be used to implement custom features in XAF, e.g. to customize a certain UI element, to change your application's flow and implement custom end-user interaction;
  • XAF provides a number of built-in Controllers and Actions that are used when generating the default UI. For instance, CRUD, validation, navigation and search features are powered by Controllers;
  • It is possible to create custom Controllers derived from built-in ones to customize the default UI generation and behavior.
  • The default XAF UI is built at runtime and consists of regular controls using the standard development techniques. The only specificity of XAF is that it focuses on reusing code where possible - think of it like about building your application from custom user controls to avoid duplicate work.
  • In XAF, you can access a required UI element or control and customize it using the approaches you are accustomed to in the traditional development with WinForms or ASP.NET frameworks. See one, two, three topics in our docs for more customization examples.

Wednesday, May 29, 2013

Discussing custom forms and controls (again)

I recently updated the E911 example to 13.1 (it was very smooth, btw - only one change from IComplexPropertyEditor to IComplexViewItem) and refactored it a bit (improved some code, added more code comments and fully rewrote the description).

I think that you may be interested in checking this updated example (I reposted its description below), because it may help you better understand ideas behind XAF as well as how it works and is built internally.

In addition, I recommend you check out the following help articles in the XAF documentation:

eXpressApp Framework > Concepts > UI Construction
eXpressApp Framework > Concepts > UI Construction > UI Element Overview
because they should give you a better understanding of reusable XAF building blocks and see how to customize or replace them.

==========================
This example implements the following scenarios when an end-user clicks on a custom item in the navigation control:
- a custom non-XAF form is opened as a result;
- a standard XAF View containing a custom user control is opened as a result.
Both custom form and user controls display persistent data from the XAF application database:


To accomplish this, you can follow the instructions below:

1. Define a base structure of the navigation control in your XAF application, as shown in the E911.Module\Model.DesignedDiffs.xafml file.
You can simply copy and paste the contents of the NavigationItems element into the corresponding file (pre-opened via the text editor) of your platform-agnostic module.
The same customizations can be achieved via the Model Editor visually.

This navigation structure will be further customized in the WinForms and ASP.NET executable projects later.

2. Intercept events of the navigation control to display a custom form when a custom navigation item is clicked.
To do this, implement a WindowController into your platform-agnostic module and handle events of the ShowNavigationItemController class as per the E911.Module\Controllers\ShowCustomFormWindowController.xx file. This controller will be abstract and be overridden in WinForms and ASP.NET modules.

3. Declare a custom ViewItem class that is supposed to host a custom user control in the standard XAF View. To do this, implement a custom ViewItem descendant and related types in the platform-agnostic module as shown in the E911.Module\Editors\CustomUserControlViewItem.xx file.
This ViewItem will also be abstract and platform-agnostic as it will not create platform-dependent controls, and will just provide a common customization code for both platforms. For instance, the OnControlCreated method will be overridden to bind the created control to data. To access persistent data from the database used by an XAF application, the ViewItem will implement the IComplexViewItem interface that consists of a single Setup method, receiving the IObjectSpace and XafApplication objects as parameters.
To unify our data binding code for both platforms, the IXpoSessionAwareControl interface and an auxiliary XpoSessionAwareControlInitializer class are introduced.
The interface provides a single UpdateDataSource method that is implemented by custom forms and user controls to bind them to data received by means of XPO.
You can use a similar mechanism and modify these auxiliary types to pass other custom data into your custom forms and controls.

4. Define a base structure of the standard XAF View with a custom ViewItem as shown in the E911.Module\Model.DesignedDiffs.xafml file.
You can simply copy and paste the contents of the Views element into the corresponding file (pre-opened via the text editor) of your platform-agnostic module.


5. Create custom forms and user controls in WinForms and ASP.NET executable projects analogous with what is shown in the example. The easiest way to do this is to copy the contents of the E911.Win\Controls and E911.Web\Controls folders and then include the necessary files into your solution. Take special note that these custom forms and controls implement the IXpoSessionAwareControl interface to automatically receive persistent data and other parameters when they are created.


6. Implement platform-dependent behavior to open and customize custom forms and controls. To do this, copy the following files into your WinForms and ASP.NET module projects:
WinForms:
E911.Module.Win\Controllers\WinShowCustomFormWindowController.xx - contains an WinForms version of the WindowController, which is inherited from the platform-agnostic
E911.Module.Win\Editors\WinCustomUserControlViewItem.xx - contains an WinForms version of the ViewItem, which is inherited from the platform-agnostic one;
E911.Module.Win\WinModuleEx.xx - contains a registration code for WinForms version of the ViewItem;
one;

ASP.NET:
E911.Module.Web\Editors\WebCustomUserControlViewItem.xx - contains an ASP.NET version of the ViewItem, which is inherited from the platform-agnostic one;
E911.Module.Web\WebModuleEx.xx - contains a registration code for ASP.NET version of the ViewItem;
E911.Module.Web\Controllers\WebShowCustomFormWindowController.xx - contains an ASP.NET version of the WindowController, which is inherited from the platform-agnostic one;

These platform-dependent versions of the WindowController and ViewItem are required to implement the creation and display of custom forms and controls using the means specific for each platform. They are also designed to provide the capability to be able to set custom forms and control settings via the Model Editor. For that purpose, custom Application Model extensions are implemented for the Navigation Item and View Item model elements.

7. Set the custom forms and controls settings for each platform.
To do this, copy the contents of the E911.Win\Model.xafml and E911.Web\Model.xafml files into the Model.xafml file in the executable WinForms and ASP.NET projects:
WinForms:


ASP.NET:



That is it.

IMPORTANT NOTES
1. It is also possible to mix the traditional and XAF development approaches (consult our Support Team if you are not sure how to integrate your standard non-XAF solution into XAF), because an XAF application is a regular .NET application built of reusable blocks like View, ViewItem, Property and List Editors, etc. that eventually create and customize platform-dependent controls exactly the same way you do this without XAF. So, using XAF does not mean something absolutely new and allows you to reuse your existing development skills and practices. Of course, it is possible to create your own reusable blocks if the standard ones do not meet your needs. For instance, the example of a custom View class designed to show a custom form can be found on CodeProject here.

2. This solution contains some generic code (e.g., base WindowController and ViewItem) that is mainly required because our XAF application is for both Windows and the Web. You may avoid this generic code and make a simpler implementation if you are developing for only one platform.

3. You can display custom forms not only when interacting with the navigation control, but from any other place. To do this, intercept the events of a required entity, e.g., an XAF Controller, Action or a View. Refer to the product documentation or consult with our Support Team in case of any difficulties.

4. By default controls layout and user customizations are preserved only for built-in XAF ListEditors, because they have special code for that. If you embed a custom user control into XAF, you need to preserve its settings yourself as well, exactly like you would do when implementing this task in the "old way" in a non-XAF application. Refer to the control's documentation to learn more on how to accomplish this task.
Feel free to contact the respective product team if you experience any difficulties customizing this control.

Thursday, April 25, 2013

Создание повторно используемых бизнес моделей с помощью технологии Domain Components (XAF)

Недавно выступал на встрече GETDEV.NET c кратким обзором нашей технологии Domain Components (DC). Вот небольшая аннотация к докладу:

"Задумывались ли вы когда-нибудь, что с переходом от SQL к DataSet, а затем и к ORM типа Entity Framework развитие технологий для доступа и управления данными приостановилось? Что еще нового можно придумать к уже привычному оперированию записями таблиц БД как объектами CRL и при этом поднять удобство разработчика на следующий уровень? На этот и другие вопросы попробует дать ответ доклад о технологии Domain Components (часть DevExpress eXpressApp Framework), которая облегчает создание повторно используемых бизнес моделей за счет легкого комбинирования путем использования интерфейсов вместо классов (это позволяет вам эмулировать "множественное наследование" в C# и VB.NET), а также свободы от особенностей конкретной ORM."

Слайды презентации можно скачать вот тут:

http://www.slideshare.net/kaatula/domain-components

Видео (примерно на час) доступно на YouTube:

P.S.
Докладчика просьба строго не судить, так как это его третье публичное выступление:-)

Thursday, October 25, 2012

Making XAF Conditional Appearance more user-friendly

Take special note of the following video:


View on screencast.com »

It shows how to customize the Conditional Appearance module to allow your end-users to easily create Appearance rules directly from Views and not by using the Model Editor tool.

Refer to the http://www.devexpress.com/issue=Q381881 ticket for the source code of this extension. In my opinion it is a very smart piece of work and your end-users will like it.

Working with links to files instead of storing their contents in the database

If you read my previous post about a reusable File System Data module I created some time ago, you might remember that it included a custom IFileData implementation (this simple interface/contract is used by our buit-in File Attachments module that provides a generic solution for working with files in both desktop and Web apps). My solution was designed to work with links to real files instead of storing their contents in the database. Before today, it had a small issue that does not allowed you to keep changes to files opened from these links (e.g., you opened a *.docx file in Word, edited it and then saved), because a temporary copy of the file was created by default. I discovered this issue with the help of a customer (thanks, Will!) and updated the E965 example accordingly. The fix was in handling the CustomOpenFileWithDefaultProgram event and using the Process.Start method. Now, everything operates as expected:


View on screencast.com »

This is just yet another example on how our eXpressApp Framework is flexible as well as on how the community feedback is important to run our mutual progress.

"Ideal" development workflow for adding a custom functionality in XAF

In this "short" post I'd like to demonstrate the subject by example of implementing the following suggestion: SystemModules.Link - Exclude objects that are already linked to an object from the Link ListView. Good for our learning purposes, this feature request is representative and small enough at the same time. Technically, we will improve the standard LinkUnlinkController class behavior. Let's start doing real coding!

Implementation/Refactoring

If you look at the current 11.2 code of the controller, you will notice that it is quite difficult to provide your own link View for it:

private void linkAction_OnCustomizePopupWindow(Object sender, CustomizePopupWindowParamsEventArgs args) {
linkListView = (ListView)CreateLinkView();
args.View = linkListView;
args.DialogController = Application.CreateController<LinkDialogController>();
((LinkDialogController)args.DialogController).Initialize(lookUpEditorHelper,
lookUpEditorHelper.CanFilterDataSource(linkListView.CollectionSource, MasterObject));
}

Note that according to this code the link View must be always a ListView. Let's fix this by adding more flexibility as follows:

private void linkAction_OnCustomizePopupWindow(Object sender, CustomizePopupWindowParamsEventArgs args) {
View linkView = CreateLinkView();
args.View = linkView;
linkListView = linkView as ListView;
if (linkListView != null && lookUpEditorHelper != null) {
LinkDialogController dialogController = Application.CreateController<LinkDialogController>();
dialogController.Initialize(lookUpEditorHelper, lookUpEditorHelper.CanFilterDataSource(linkListView.CollectionSource, MasterObject));
args.DialogController = dialogController;
}
}

As you see, I removed the direct cast to ListView and also slightly refactored the rest code. Apparently, now the linkListView variable must be checked on null before using.

If you look at the current CreateLinkView method code, you will notice that it creates the link View of the ListView type only. Since we want to provide the capability to create the link View of an arbitrary View type (e.g., check out this ticket for a real business scenario), we can refactor the method by introducing the event and also moving the default ListView creation code into a separate method:

protected virtual View CreateLinkView() {
CustomCreateLinkViewEventArgs customArgs = new CustomCreateLinkViewEventArgs(View);
OnCustomCreateLinkView(customArgs);
if (!customArgs.Handled || customArgs.LinkView == null)
return CreateLinkViewCore();
return customArgs.LinkView;
}
protected virtual void OnCustomCreateLinkView(CustomCreateLinkViewEventArgs customArgs) {
if (CustomCreateLinkView != null)
CustomCreateLinkView(this, customArgs);
}

The new event and its arguments declarations will look as follows:

public event EventHandler<CustomCreateLinkViewEventArgs> CustomCreateLinkView;

public class CustomCreateLinkViewEventArgs : HandledEventArgs {
private ListView sourceViewCore;
public CustomCreateLinkViewEventArgs(ListView sourceView) {
sourceViewCore = sourceView;
}
public ListView SourceView { get { return sourceViewCore; } }
public View LinkView { get; set; }
}


To complete support of custom link Views, we need to slightly refactor the Link method code, because it currently takes selected objects from the default ListView only:

protected virtual void Link(PopupWindowShowActionExecuteEventArgs args) {
LinkObjects(args.PopupWindow.View.SelectedObjects);
}

My refactored code is shown below:

protected virtual void Link(PopupWindowShowActionExecuteEventArgs args) {
QueryLinkObjectsEventArgs customArgs = new QueryLinkObjectsEventArgs(args.PopupWindow);
OnQueryLinkObjects(customArgs);
IList linkObjects = customArgs.LinkObjects;
if (!customArgs.Handled && linkListView != null)
linkObjects = linkListView.SelectedObjects;
LinkObjects(linkObjects);
}
protected virtual void OnQueryLinkObjects(QueryLinkObjectsEventArgs customArgs) {
if (QueryLinkObjects != null)
QueryLinkObjects(this, customArgs);
}

Here the QueryLinkObjects event and its arguments look as follows:

public event EventHandler<QueryLinkObjectsEventArgs> QueryLinkObjects;
public class QueryLinkObjectsEventArgs : HandledEventArgs {
private Window linkWindowCore;
public QueryLinkObjectsEventArgs(Window linkWindow) {
linkWindowCore = linkWindow;
}
public Window LinkWindow {
get { return linkWindowCore; }
}
public IList LinkObjects { get; set; }
}

So, a guy who wants to implement a custom link View will have to handle these two events, provide their respective LinkView and LinkObjects parameters and do not forget to set the important Handled parameter to True.

We are now done with the first part and are ready to implement the main functionality - excluding already linked objects from the link View. Apparently, we need to modify the newly introduced CreateLinkViewCore method for that purpose. Since we are creating a ListView in this method, we need to add a criterion to its CollectionSource, as described in this help article from our docs:

if(MasterObject != null) {
CriteriaOperator associatedCollectionCriteria =
ObjectSpace.GetAssociatedCollectionCriteria(MasterObject, ((PropertyCollectionSource)View.CollectionSource).MemberInfo);
result.CollectionSource.Criteria[CriteriaKeyForLinkView] = new NotOperator(associatedCollectionCriteria);
}

As you see, I just took association criteria and then inverted it.

It seems that we are done with our main implementation and now it is time to check whether this code ever works. The easiest way to do this is to run our MainDemo application and test the improved Link functionality with Contact and its Tasks list:

Linkunlinktest

Thanks God, my code worked like a sharm in both Windows Forms and ASP.NET applications. Some of you may think that we are now fully done, but in fact we are only in the middle of our path... Why? The answer is simple - we must ensure that the code we just wrote will work fine in future versions of our product, preferably without any input from God.

Unit Testing

The best way to ensure this is to cover the implemented functionality with unit and functional tests. I strongly believe that it is simply damn wrong and plain stupid not to do this (no mercy) and as a result manually test this functionality each and every release in the future. Writing tests will also make you a better developer.

Fortunately for me (and for you too!), the developers of our Core team already had some unit tests for LinkUnlinkController in the LinkUnlinkControllerTests class and my task is to just add a few test methods to cover my code. Practically, that means that I do not need to create a required testing infrustructure from scratch and can just reuse existing test objects and probably grab some example code blocks from other tests. Another good thing is that I can make use of existing base test classes and mocks like BaseXafTest, TestApplication, etc. that provide useful methods and allow me to write platform-independent tests. Let's demonstrate this by an example.

First, I will extend the LinkUnlinkController descendant used only for tests with a couple of overridden methods to provide counters for my events - I want to ensure that they actually fire when needed and their arguments are taken into account:

public class LinkUnlinkControllerForTests : LinkUnlinkController {
public int CustomCreateLinkViewEventCounter = 0;
public int QueryLinkObjectsEventCounter = 0;
public LinkUnlinkControllerForTests() : base() { }
protected override void OnCustomCreateLinkView(CustomCreateLinkViewEventArgs customArgs) {
base.OnCustomCreateLinkView(customArgs);
CustomCreateLinkViewEventCounter++;
}
protected override void OnQueryLinkObjects(QueryLinkObjectsEventArgs customArgs) {
base.OnQueryLinkObjects(customArgs);
QueryLinkObjectsEventCounter++;
}
}

This is a very common practice, especially when we need to hook up into some protected members or disable/mock certain functionality for tests only. To give you one more example, the TestApplication class I mentioned earlier is a descendant of XafApplication that overrides its methods for tests, e.g. provides platform-agnostic TestListEditor and TestLayoutManager instead of real platform-dependent classes.

Now we are ready to write our first test method for the functionality implemented earlier:

[Test]
public void TestCustomCreateLinkViewEvent() {
RegisterTypesForModel(typeof(Incident), typeof(Message));
CollectionSourceBase linkedMessagesDS = CreateCollectionSource(incident, "Messages");
TestApplication application = new TestApplication(this, modelApplication);
LinkUnlinkControllerForTests controller = new LinkUnlinkControllerForTests();
controller.Application = application;
ListView sourceListView = application.CreateListView(GetListView<Message>(), linkedMessagesDS, false);
sourceListView.CreateControls();
controller.SetView(sourceListView);
Assert.AreEqual(controller.LinkAction.GetPopupWindowParams().View.GetType(), typeof(ListView));
Assert.AreEqual(1, controller.CustomCreateLinkViewEventCounter);
bool handled = false;
EventHandler<CustomCreateLinkViewEventArgs> customCreateLinkViewEventHandler = delegate(object sender, CustomCreateLinkViewEventArgs args) {
Assert.AreEqual(args.SourceView, sourceListView);
args.LinkView = new DashboardView(ObjectSpace, application, false);
args.Handled = handled;
};
controller.CustomCreateLinkView += customCreateLinkViewEventHandler;
Assert.AreEqual(controller.LinkAction.GetPopupWindowParams().View.GetType(), typeof(ListView));
Assert.AreEqual(2, controller.CustomCreateLinkViewEventCounter);
handled = true;
Assert.AreEqual(controller.LinkAction.GetPopupWindowParams().View.GetType(), typeof(DashboardView));
Assert.AreEqual(3, controller.CustomCreateLinkViewEventCounter);
controller.CustomCreateLinkView -= customCreateLinkViewEventHandler;
}

It's a bit too long, but I need to test all possible combinations.

The next two test methods will be a way more complex and longer:

[Test]
public void TestQueryLinkObjectsEventAndExcludeLinkedObjectsNoServerMode() {
TestQueryLinkObjectsEventAndExcludeLinkedObjectsCore(false);
}
[Test]
public void TestQueryLinkObjectsEventAndExcludeLinkedObjectsServerMode() {
TestQueryLinkObjectsEventAndExcludeLinkedObjectsCore(true);
}

private void TestQueryLinkObjectsEventAndExcludeLinkedObjectsCore(bool useServerMode) {
RegisterTypesForModel(typeof(Incident), typeof(Message));
CollectionSourceBase linkedMessagesDS = CreateCollectionSource(incident, "Messages");
TestApplication application = new TestApplication(this, modelApplication);
application.Model.Views.DefaultListEditor = typeof(TestListEditor);
application.Model.Options.UseServerMode = useServerMode;
LinkUnlinkControllerForTests controller = new LinkUnlinkControllerForTests();
controller.Application = application;
ListView sourceListView = application.CreateListView(GetListView<Message>(), linkedMessagesDS, false);
sourceListView.CreateControls();
controller.SetView(sourceListView);
CustomizePopupWindowParamsEventArgs windowParams = controller.LinkAction.GetPopupWindowParams();
Assert.AreEqual(1, linkedMessagesDS.GetCount());
Assert.AreEqual(windowParams.View.GetType(), typeof(ListView));
Assert.AreEqual(0, controller.QueryLinkObjectsEventCounter);
bool handled = false;
Window linkWindow = new Window(application, "", null, false, true);
EventHandler<QueryLinkObjectsEventArgs> queryLinkObjectsEventHandler = delegate(object sender, QueryLinkObjectsEventArgs args) {
args.LinkObjects = new object[0];
args.Handled = handled;
Assert.AreEqual(linkWindow, args.LinkWindow);
Assert.AreEqual(windowParams.View, args.LinkWindow.View);
};
controller.QueryLinkObjects += queryLinkObjectsEventHandler;
ListView linkListView = ((ListView)windowParams.View);
CollectionSourceBase messagesToLinkDS = linkListView.CollectionSource;
Assert.IsTrue(messagesToLinkDS.Criteria.ContainsKey(LinkUnlinkController.CriteriaKeyForLinkView));
Assert.AreEqual(0, messagesToLinkDS.GetCount());
Message messageToLink = new Message(ObjectSpace.Session);
ObjectSpace.CommitChanges();
messagesToLinkDS.Reload();
Assert.AreEqual(1, messagesToLinkDS.GetCount());
linkWindow.SetView(linkListView);
linkListView.CreateControls();
linkListView.Editor.FocusedObject = messageToLink;
controller.LinkAction.DoExecute(linkWindow);
Assert.AreEqual(1, controller.QueryLinkObjectsEventCounter);
ObjectSpace.CommitChanges();
incident.Reload();
Assert.AreEqual(2, incident.Messages.Count);
handled = true;
messageToLink = new Message(ObjectSpace.Session);
ObjectSpace.CommitChanges();
linkListView.Editor.FocusedObject = messageToLink;
controller.LinkAction.DoExecute(linkWindow);
Assert.AreEqual(2, controller.QueryLinkObjectsEventCounter);
ObjectSpace.CommitChanges();
incident.Reload();
Assert.AreEqual(2, incident.Messages.Count);
controller.QueryLinkObjects -= queryLinkObjectsEventHandler;
}

However, these methods fully test the entire link functionality from the beginning to end. It is also done under both server-side and client binding modes.

Did I mention that writing good unit tests once and for all will help you avoid problems and PITA in the future?

Functional Testing

Finally, to ensure that everything will operate as expected under real circumstances, I will add functional tests. As you probably know, these kind of tests is powered by our EasyTest functional testing framework. With EasyTest I can ensure the correctness of both Windows Forms and ASP.NET applications using a single test script:

#Aplication AppStudioTestWeb
#Application AppStudioTestWin

*Action Navigation(Incident)
*ProcessRecord
Описание = Test Incident

*Action Versions

*Action Versions.Link
*CheckTable
Columns = Name
Row = 1.0
Row = 2.0
*ProcessRecord
Name = 2.0

*CheckTable Versions
Columns = Name
Row = 2.0

*Action Versions.Link
*CheckTable
Columns = Name
Row = 1.0
*Action Cancel

*SelectRecords Versions
Columns = Name
Row = 2.0
*Action Versions.Unlink

*HandleDialog
Respond = Yes

!ProcessRecord Versions
Name = 2.0

That is it. Now I am sure that the code I added will not be broken one day. Or at least I will know if that happens from failed tests.

I hope you find this information interesting as the process I described above will help you ensure a better quality of your XAF applications. I also want to explicitly mention that this process is the same in our team when developing features. The only thing I did not mention is that we also do a manual testing of some scenarios before releasing a new product version.

Forgot to say, that the feature I described here will be available in XAF 12.1. I also hope you also liked this small addition to the framework.

See Also
Best practices of creating reusable XAF modules by example of a View Variants module extension
eXpressApp Framework > Task-Based Help > Testing

How to manage users (register a new user, restore a password, etc.) from the logon form

I have just finished refactoring a cool example, which I originally posted to this forum thread two years ago.

You can download the updated version from here. Here is also a small video that shows the implemented functionality in action:


View on screencast.com »

If you decide to use this module in your solution, do not forget to integrate functional tests (E4037.ets) as well.

I hope you find it helpful.