Quantcast
Channel: eXpandFramework RSS

BLOG by Robert Anderson: Migrating a large web application from XAF 12.1 to 15.1 - Part 2

$
0
0

This is the second part of a series about migrating a large application from XAF 12.1 to XAF 15.1.

In the 13.1 release, DevExpress made a change to the way XAF Validator class is used. It now requires an IObjectSpace parameter corresponding to the object. It is needed to correctly evaluate any rules which are descendants of the RuleSearchObjectProperties. These include:

  • RuleCombinationOfPropertiesIsUnique
  • RuleIsReferenced
  • RuleObjectExists
  • RuleUniqueValue

A lot of our code has been around for years now and the older parts rely heavily on Session and UnitOfWork instead of IObjectSpace For the most part our application used IObjectSpace only within ViewControllers.

But there were several situations where we need the validator where we don’t have an IObjectSpace. For instance we sometimes need to validate from within method actions (decorated with the ActionAttribute). For performance reasons, we pass criteria to our middleware and it uses a UnitOfWork to run the method on each object. So in this case, there was no IObjectSpace to pass to the XAF Validator.

Here I had a refactoring dilemma to solve. Either I need to rewrite all of the affected rules so that they no longer make use of the IObjectSpace. For instance, I could use RuleFromBoolProperty instead. In our application, this would mean rewriting about 50 rules. Or alternatively, I could go through the entire code base looking for new UnitOfWork() and new Session() and try to use an IObjectSpace instead. When writing code I often find myself having to decide between the ‘quick’ fix and the ‘right’ fix. Here, moving to IObjectSpace throughout is clearly the right fix and although it would take more time to implement, the system will be more in-line with best XAF practices throughout.

Eventually, the refactoring was complete and all unit tests are passing. I was eager to run a multi-user load stress test against the 15.1 version to compare performance under load. I have described in previous posts how to stress test XAF applications. I’ll be sharing the results in the next post.


BLOG by Robert Anderson: Migrating a large web application from XAF 12.1 to 15.1 - Part 3

$
0
0

This is the third part of a series about migrating a large application from XAF 12.1 to XAF 15.1. In this part I will compare the results of a simple stress test between the versions.

I have described in previous posts how to stress test XAF applications. One of our most basic tests is to simulate 25 users cycling through all the navigation tabs for an hour. I’m happy to report there is a considerable improvement under load in version 15.1.

(Note that we purposefully stress test against a single web application so that we can compare apples with apples. In production we have multiple instances load-balanced.)

Here is an interactive summary of the 15.1 results:

Here is version 15.1. There were zero errors and 382 completed scripts.

By comparison, the same test against DevExpress 12.1 yielded only 258 completions. So 15.1 shows a 48% performance improvement over 12.1.

A review of NDepend 5

$
0
0

NDepend is a commercial static analysis tool for .NET managed code. It’s been around a long time (since 2004!). Version 5 was just released and in this post I’m going to try it out on the DevExpress MainDemo.

In the past I have always thought of NDepend as a complex tool. I was never sure where to start. In version 5, a lot of work has been done to improve the learning curve. The installation process is easy and a wizard very quickly points you in the right direction.

After downloading the v5 trial and running the installation you get to the following screen.

You cannot miss that big red arrow. In fact, it’s even animated in the actual product. Click on it and choose a Visual Studio solution to analyse. I’m going to navigate to the DevExpress MainDemo 13.1.

So long as the project has been built at some point, NDepend works out what to analyse (otherwise you’ll get helpful warnings).

It is very fast and we get to this help screen.

Choose the dashboard.

Now that’s a lot of information, but it’s all well presented and easy to navigate. Let’s focus on the worst: I can see 2 Critical Rules Violated. Drill down to find out more:

It’s complaining that we have classes with duplicate names in our projects. In the pane on the left we can see what they are: 3 types named MainDemoWinApplication, 3 named Program, etc. And sure enough there are: MainDemo.Win, MainDemo.Win.Mdi and MainDemo.Win.Ribbon all duplicate those class names.

We can also see 2 TaskAnalysis1LayoutUpdater types and a quick search reveals that there’s one in the web module and another in the win module.

So NDepend has correctly discovered some potential issues. As we XAF fans know, this one is not really a problem, because those modules are never loaded into the same AppDomain, but nevertheless the information is accurate and relevant.

Lets have a brief look at the other screens. The dependency graph:

With a million options.

A dependency matrix.

A metrics view showing class and assembly sizes.

All with reams of helpful documentation.

Overall, the tool felt fast, responsive and stable. I’ve focused on the user interface aspects, but there is so much more. Some things you can do:

NDepend shines at providing a high-level overview of code quality and as such it is a very useful addition to any developer’s toolkit. There are some scenarios where NDepend would be particularly useful:

  • For a developer joining a mature project.
  • For a senior developer looking to track progress on a refactoring drive.
  • For helping evaluate the quality of an open source third party library.

In this quick review, I’m not going deep enough to say anything about whether the DevExpress MainDemo is good code or not - it’s just a sample project I happen to be quite familiar with. It might be interesting to unleash NDepend on the full DevExpress source code and maybe one day I’ll get around to writing a future post about that.

With regard to my own projects, I feel I’m so familiar with them that I ought to be aware of most of the recommendations NDepend is likely to make, but I’ll give it a spin and see what comes out…

The Ugly, the Heavy and the Good: 3 solutions to an XAF layout problem

$
0
0

This post aims to look at various solutions to a seemingly simple layout change.

With DevExpress XAF, a lot of the difficult things are easy: authentication, authorisation, ORM, reporting, complex form layouts, themes, etc., are all made easy by XAF. On the other hand, some of the easy things are hard. It can be frustratingly difficult to make a small modification to the basic layout. We will look at an example of such a change and evaluate the recommended DevExpress approaches.

The problem

Whenever my XAF web application asks me to change my password, I get a screen like this:

Well that’s a bit annoying - those edit boxes are far too wide. Wouldn’t it look much better if it were narrower and centered? If we weren’t using XAF, we’d probably have a separate aspx file for this view and we could just modify the html. With XAF there are several different recommended ways of modifying the output.

Option 1: Use the model

Well, one approach would be to use the layout designer to add some EmptySpaceItems to the default layout for the ChangePasswordOnLogon. Add something like this to MainDemo.Web/Model.xafml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  <Views>
    <DetailView Id="ChangePasswordOnLogonParameters_DetailView">
      <Layout>
        <LayoutGroup Id="Main" Removed="True" />
        <LayoutGroup Id="Narrow" RelativeSize="50" HorizontalAlign="Center" Direction="Horizontal" IsNewNode="True">
          <LayoutItem Id="EmptySpaceItem1" Index="0" ViewItem="" RelativeSize="25" IsNewNode="True" />
          <LayoutGroup Id="Main" ShowCaption="False" Index="1" IsNewNode="True">
            <LayoutGroup Id="Static" Index="0" Direction="Horizontal" ShowCaption="False" IsNewNode="True">
              <LayoutItem Id="ChangePasswordImage" Index="0" ShowCaption="False" RelativeSize="1" ViewItem="ChangePasswordImage" IsNewNode="True" />
              <LayoutItem Id="ChangePasswordText" Index="1" ShowCaption="False" RelativeSize="99" ViewItem="ChangePasswordText" IsNewNode="True" />
            </LayoutGroup>
            <LayoutGroup Id="SimpleEditors" Index="1" ShowCaption="False" IsNewNode="True">
              <LayoutGroup Id="ChangePasswordOnLogonParameters" ShowCaption="False" Index="0" IsNewNode="True">
                <LayoutItem Id="NewPassword" ViewItem="NewPassword" Index="0" IsNewNode="True" />
                <LayoutItem Id="ConfirmPassword" ViewItem="ConfirmPassword" Index="1" IsNewNode="True" />
              </LayoutGroup>
            </LayoutGroup>
          </LayoutGroup>
          <LayoutItem Id="EmptySpaceItem2" Index="2" ViewItem="" RelativeSize="25" IsNewNode="True" />
        </LayoutGroup>
      </Layout>
    </DetailView>
  </Views>

Ugh. What a lot of work for such a small change. Another approach would be to make an equivalent model modification in code by subclassing ModelNodesGeneratorUpdater<ModelViewsNodesGenerator> but it would be even more effort.

Is it easy to maintain? Not especially. If we make any changes to the parameters object, we would have to update the layout again. It is quite likely we’d have to make revisions when upgrading the framework.

And does it work?

No!. It’s a little better but it still looks ugly because the OK and Cancel buttons are still out wide.

Verdict: too ugly

When should you use this approach?:

  • When the layout changes are within the main view area.

Let’s look at another option.

Option 2: Customise the ASP.NET template

If we were to follow the instructions here we can modify the HTML exactly as we want. Unfortunately, this is even more work. We would need to:

  • Create a new MyNarrowTemplateDialogContent.ascx
  • Modify the HTML within it to add a width to Form1
  • Find some way of applying this template to only the detail view for Change Password. This is currently not easy but there is an example project at the end of this issue.

We’d end up with quite a few new files to maintain for just one little layout fix…

Also, another problem with this approach is that it needs reviewing whenever a new version of the framework is released, because the default templates may have changed. Too much maintenance work for such a little change.

Verdict: too heavy

When should you use this approach?:

  • When the same layout changes are to be applied to the views of all or many different object types.
  • When the changes you are making are significant enough that they are hard to achieve by the solution below.

Option 3: Use a ViewController

The ViewController and WindowController are well-suited to this sort of task.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class NarrowDialogViewController : ViewController<DetailView>
{
    protected override void OnActivated()
    {
        base.OnActivated();
        Active["ShouldUseNarrowForm"] = View.ObjectTypeInfo.Type == typeof(ChangePasswordOnLogonParameters); // see side-note below
    }

    protected override void OnViewControlsCreated()
    {
        base.OnViewControlsCreated();
        if (WebWindow.CurrentRequestPage != null)
        {
            var htmlForm = WebWindow.CurrentRequestPage.FindControl("Form2") as HtmlForm; // see note below
            if (htmlForm != null)
            {
                // make the form narrow
                htmlForm.Attributes.CssStyle.Add("width", Unit.Percentage(40).ToString());
                // center the form
                htmlForm.Attributes.CssStyle.Add("margin-left", "auto");
                htmlForm.Attributes.CssStyle.Add("margin-right", "auto");
            }
        }
    }
}

A side note: do not use TargetObjectType == typeof(ChangePasswordOnLogonParameters) instead of the OnActivated() override. This is because ChangePasswordParameters which is used when the user clicks on Change My Password is a subclass of ChangePasswordOnLogonParameters and we do not want that view to be narrowed because it appears in a popup window instead of the main window.

This solution is quite maintainable. We can delimit the views for which the modification applies making the controller is active. And the modification itself is relatively simple. It is also reasonably robust with regard to DevExpress upgrades. (They did just change the name of the main dialog Form Form1 in 12.1 to Form2 in 12.2, but it was the first time in years and it was simple to fix. Alternatively, you could use FindControl instead of using the hard-wired name).

Here’s the resulting output, horizontally centered in the browser window:

Verdict: good

When should you use this approach?:

  • When a layout change is easy to apply by modifying styles. When a change pertains only to one or few specific object types.

Conclusions

In this case, using a ViewController leads to relatively simple code and ease of maintenance. Other situations may well be better served by the other approaches.

DevExpress XAF is a powerful mature product. As such, it can be daunting to new users because of all the different approaches for applying changes. It is always possible to find an elegant maintainable solution, but sometimes it is not obvious, even when the required change is small.

Three ways to store a list of currency codes in XAF

$
0
0

In the last post we looked at three solutions to a relatively simple XAF requirement. In this post I’ll discuss another XAF challenge, explain the options and provide a versatile and maintainable solution.

In my DevExpress XAF application, I have an object which has several properties like this:

In each case, the field is a comma-separated list of currency codes. These fields are not very important to the model - they are used mainly for reporting.

Let’s look at 3 different ways of handling these fields.

Option 1 - Use a string field

The lightest option would be just to declare them as a normal XPO string field:

1
2
3
4
5
6
7
8
9
10
11
12
private string _List1Currencies;
public string List1Currencies
{
    get
    {
        return _List1Currencies;
    }
    set
    {
        SetPropertyValue("List1Currencies", ref _List1Currencies, value);
    }
}

It’s certainly simple and maintainable, but it’s not very user-friendly. There is no fancy interface to help with the input. We can perhaps improve things slightly by providing edit masks and/or validation rules to check the input, but careful typing is the only way to change the values.

Option 2 - Declare an association property

The heaviest option is to declare each such property as a many-to-many relationship.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Container: XPObject {
    public Container(Session session) : base(session) { }

  public string Name;

    [Association("List1Currencies")]
    public XPCollection<Currency> List1 {
        get { return GetCollection<Currency>("List1"); }
    }
}

public class Currency: XPObject {
    public Currency(Session session) : base(session) { }

    [Size(3)]
    public string Code;

  public string Name;

    [Association("List1Currencies")]
    public XPCollection<Container> List1Container {
        get { return GetCollection<Container>("List1Container"); }
    }
}

This works great - we get a nice interface for selecting the currencies and the end result looks like this:

However, it’s quite a heavy solution for something quite simple. For each such relationship XPO will generate a new intermediate table. If we look at the database schema, we see the following:

And in the model there are two new views.

If we have 5 such properties, we end up with 5 intermediary tables and 10 new views.

Now, depending on your requirements that may be acceptable. If those relationships are important to your model, then the overhead may be justified. In my situation, these are minor fields and I do not want to burden the model or the database with extra complexity if I can avoid it.

Option 3 - Create a custom property editor

With the help of the documentation and old Support Center issues, I was able to quite quickly put together a custom editor which gives the end user a nice interface while keeping it simple. The bulk of the logic is in the SerializedListPropertyEditor base class (see the end of the article for the link to the code), but the principle is as follows:

Create a new subclass:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[PropertyEditor(typeof(String), false)]
public class CurrencyListPropertyEditor : SerializedListPropertyEditor<Currency>
{
    public CurrencyListPropertyEditor(Type objectType, IModelMemberViewItem info)
        : base(objectType, info) { }

    protected override string GetDisplayText(Currency currency)
    {
        return String.Format("{0}\t{1}", currency.Code, currency.Name);
    }

    protected override string GetValue(Currency currency)
    {
        return currency.Code;
    }
}

Then decorate each property with a ModelDefault attribute to set the PropertyEditorType.

1
2
3
4
5
6
7
8
9
10
11
12
13
private string _List1Currencies;
[ModelDefault("PropertyEditorType", "Solution1.Module.Web.CurrencyListPropertyEditor")]
public string List1Currencies
{
    get
    {
        return _List1Currencies;
    }
    set
    {
        SetPropertyValue("List1Currencies", ref _List1Currencies, value);
    }
}

Now the user gets a pretty editor to select the currencies, but the field is just a string field.

The editor supports use of the [DataSourceProperty] and [DataSourceCriteria] properties too, so you can easily filter the collection.

It is easy to provide a similar editor for any object type - just create a new subclass of SerializedListPropertyEditor<T> where T is your persistent type.

You can download a working sample project on GitHub.

How to improve XAF grid layout for Chrome

$
0
0

This post proposes a workaround for a specific XAF rendering problem related to recent versions of Google Chrome.

Here is an XAF list view as it looks with IE 10 and DevExpress 13.1.8.

This is how the same view looks with Chrome 30. Notice how the column widths are rendered poorly. The minimum space is given to the string field and the date and bool fields are too wide.

What’s going on? An update to the Chrome browser occurred in which has caused problems with the rendering of grid views in certain situations. The problem exists in all versions of Chrome great than 25 and also affects the Google Chrome Frame plugin for IE.

DevExpress were able to fix some scenarios in 13.1.4 and 12.2.11 (such as column sorting), but there are other situations which still pose problems (e.g. the fix does not resolve the problem if the Auto Filter Row is enabled, which is the case above.)

Hopefully the Chrome devs will one day fix the root problem. You can keep an eye on the issue here

The fix

Not so much a fix as a workaround. We use a ViewController<ListView> to set the grid layout to UseFixedTableLayout, but only for Chrome browsers. Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/// <summary>
/// Switches to fixed table layout for the main list view when using Chrome or ChromeFrame.
/// </summary>
public class ChromeSpecificListViewController : ViewController<ListView>
{
    protected override void OnViewChanging(View view)
    {
        base.OnViewChanging(view);
        Active["IsChrome"] = IsChrome;
    }

    private static bool IsChrome
    {
        get
        {
            HttpContext context = HttpContext.Current;
            return context != null && context.Request.Browser.Browser == "Chrome";
        }
    }

    protected override void OnViewControlsCreated()
    {
        base.OnViewControlsCreated();

        ASPxGridListEditor listEditor = View.Editor as ASPxGridListEditor;
        if (listEditor == null)
            return;

        ASPxGridView gridView = listEditor.Grid as ASPxGridView;
        if (gridView == null)
            return;

        gridView.Settings.UseFixedTableLayout = true;
    }
}

The resulting view in Chrome:

That looks better. You may notice the columns will allways be the same width now, which is not quite as good as the IE rendering. As such, it may not be appropriate for all views. But since it’s a ViewController it’s easy to deactivate it by adding an Active[] criteria.

References

DevExpress 13.2 Review - Part 1

$
0
0

The first version of XAF I ever installed was called eXpressApp-1.0.CTP2.exe in July 2006! It has certainly come a long way since then.

This post is the first part of an overview of the brand new version XAF 13.2 which will be released any day now. This part is an in-depth review of the new report writer features (still in beta).

Reports V2

Prior to this version, XAF reports were not source code. They were objects serialized to a file and then loaded into the database during initialisation. This has a lot of shortcomings some of which I have addressed in previous blog posts.

But now finally it is now possible to build XAF reports directly from Visual Studio. DevExpress is calling this Reports V2 and it largely supersedes my previous workarounds. The immediate advantages of Reports V2 are:

  • Build time syntax checking
  • Easily merge/compare report versions
  • Painless version control of reports
  • Better unit testing possibilities

Let’s have a look at the details. DevExpress have provided us with a new sample project called ReportsV2Demo. The interesting part of the project solution is here:

Notice that there are two folders containing reports. PredefinedEditableReports contains V1 reports. That is, the report is serialised as a string and loaded into the database during initialisation. If we look at the source for one of these, here is what it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace ReportV2Demo.Module.PredefinedEditableReports {
    public class OrdinalReportResource {
        public static string ReportLayout = @"
/// <XRTypeInfo>
///   <AssemblyFullName>DevExpress.XtraReportsVSuffix, Version=dllVersion, Culture=neutral, PublicKeyToken=dllPublicKeyToken</AssemblyFullName>
///   <AssemblyLocation>C:\Windows\Microsoft.Net\assembly\GAC_MSIL\DevExpress.XtraReportsVSuffix\v4.0_dllVersion__dllPublicKeyToken\DevExpress.XtraReportsVSuffix.dll</AssemblyLocation>
///   <TypeName>DevExpress.XtraReports.UI.XtraReport</TypeName>
///   <Localization>en-US</Localization>
///   <Version>VersionShort</Version>
///   <References>
///     <Reference Path=""C:\Windows\Microsoft.Net\assembly\GAC_MSIL\DevExpress.Persistent.BaseVSuffix\v4.0_dllVersion__dllPublicKeyToken\DevExpress.Persistent.BaseVSuffix.dll"" />
///     <Reference Path=""C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll"" />

/// etc.

The second folder, PredefinedReadonlyReports contains the V2 reports. With these reports, we can right-click and View Designer and up pops the XtraReport designer.

Now I can make changes to my report within Visual Studio. In theory I can preview the report and view the HTML output. However, in the beta (13.2.4) the Preview view prints the report correctly but fails to fetch any data so all the fields are empty (maybe this is by design).

(Actually, I think the names of these folders could be improved. Maybe XafReports vs. XtraReports. Or RuntimeReports vs. DesigntimeReports.)

With respect to event scripts which in the past were particularly arduous to maintain, we now have two options.

Scripts option 1 - Use serialized scripts

Firstly, you can continue to use the scripts as before. You can use Visual Studio’s Properties window to navigate to the script you need and then add the necessary code in the custom editor.

Unfortunately, this is not the best from a maintenance perspective. The Scripts code editor view is not at all comparable to Visual Studio. No Intellisense. No CodeRush. In addition, the script itself gets serialized to a string.

1
2
this.ScriptsSource = "\r\nprivate void xrLabel2_BeforePrint(object sender,System.Drawing.Printing.PrintE" +
    "ventArgs e) {\r\n    string something = \"something\";\r\n}\r\n";

So unfortunately this approach does not improve things much with regard to compile-time syntax checking or code merging/version control.

Scripts option 2 - Move the scripts to the C# file

Now that the report is more like an XtraReport, we can attach an event in code. To test it I’ll replace the output of the First name with the current time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public partial class XtraReportOrdinary : DevExpress.XtraReports.UI.XtraReport
{
    public XtraReportOrdinary()
    {
        InitializeComponent();
        xrLabel2.BeforePrint += xrLabel2_BeforePrint;
    }

    private void xrLabel2_BeforePrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        // Today's date and time
        xrLabel2.Text = DateTime.Now.ToString("G");
    }
}

Now let’s see if it works. We start up the web application and run the report.

Excellent! You can see the report has successfully run the event and is displaying the current time instead of the first name. Here are the advantages of handling events this way.

  • If there’s a mistake in an event, the project will not compile until it’s fixed.
  • The code is more readable.
  • The source code of the events can easily be compared with older versions.
  • The source code can be merged much more easily when handling version control conflicts.
  • Unit testing of the events becomes feasible.

Runtime tailoring

Can an end-user still make a copy and tailor the report as they could with the older versions? Yes they can! Let’s start up the WinForms client and have a look.

Each of the PredefinedReadonlyReports has a new option Copy predefined report. Also, notice that the Show Report Designer is greyed out. Now compare with the copy.

This time it is the Copy predefined report is greyed out and the familiar Show Report Designer has become available. (A little suggestion to the DevExpress crew. It would be better if the new copy got renamed to Copy of Inplace Report like Windows does when copying a file). Now we can open the Report Designer and customise the report further. Fantastic!

Let’s check what happened to our events?

Well, the serialized event was copied correctly, but the script that we put in the C# file has not been copied. A bit unfortunate - that event would have to be re-written or copied manually from the C# source file, but in most cases, additional tailoring of reports is a one-off occasional activity. For me at least, the benefits of having more maintainable code outweigh the inconvenience of losing the events when cloning a report.

Conclusions

Reports V2 is certainly the most significant improvement to the XAF reporting engine yet. I’m sure there are a few beta issues to iron out and a few further improvements down the line. In this post, I haven’t looked at features such as views, sub-reports and custom reporting components, all of which deserve further investigation.

In my main development project, there are currently 110 different reports and maintaining them takes some effort. Reports V2 will help considerably. Hats off to the DevExpress team.

Next up

In my next post I’ll be taking a look at the new soft validation along with some other 13.2 improvements.

DevExpress 13.2 Review - Part 2

$
0
0

This is the second part of a review of the new DevExpress 13.2. In the last part we looked in-depth at the new Reports V2. In this part I’ll go over some of the other new features including the support for warnings and confirmations in the validation module.

Soft Validation Rules

With 13.2, DevExpress adds support for warning/confirmation messages to the validation engine. Warnings can be used to handle an unusual but valid data entry. An example would be:

The date of birth results in an age of over 100. Are you sure?

Here the age of the contact is unusual but not impossible, so instead of prohibiting it entirely, we ask the user for confirmation before saving.

Let’s add this rule to the MainDemo. Open the model and navigate to the Validation node. Add a RuleValueComparison and configure it as follows

Of course you could instead also define the same rule with an attribute on the Birthday property. Something like:

1
2
3
4
5
6
[RuleValueComparison("IsOlderThan100_Warning", 
    DefaultContexts.Save,
    ValueComparisonType.GreaterThan, "AddYears(Now(), -100)",
    "Birthday makes this Contact older than 100. Are you sure?",
    ParametersMode.Expression,
    ResultType = ValidationResultType.Warning)]

Notice the new ResultType parameter is set to ValidationResultType.Warning.

Another typical use is to provide better handling of duplicates. Consider the following:

1
2
3
4
5
6
7
[RuleCombinationOfPropertiesIsUnique("DuplicateName_Warning", 
    DefaultContexts.Save, 
    "LastName;FirstName", 
    "There is already a Contact with the name {FullName}. Are you sure?", 
    ResultType = ValidationResultType.Warning)]
public class Contact : Person {
  //etc ...

And then this is what happens if I try to add another John Nilsen.

Another scenario would be to warn the user of something which needs attention. Such as “Warning! You are running out of funds in your current account.” Or “Warning! Deleting this record will format your hard drive.”

List Views

Soft validation also works in the list views, even with multiple selection, but there are a couple of things that don’t work very smoothly yet and I would expect the web implementation to evolve over the coming releases.

In order to demonstrate this I need to use a context which allows multiple selection such as deletion. So let’s decorate our class with the following simple rule.

1
[RuleCriteria("Deletion_Warning", DefaultContexts.Delete, "1=0", "Warning! Are you sure?", ResultType = ValidationResultType.Warning)]

Then I select all the contacts and press Delete, after the confirmation window, I get this:

Web Application

Soft validation is also available in the web application. This is what a warning looks like.

I would prefer to see a Confirm button rather than the current Ignore checkbox since a button requires a single click to validate.

When there are several warnings and errors at the same time, the current implementation displays them all. I think it would be preferable if warnings were not displayed unless there are no errors. Unless DevExpress provide this as an option soon, I will attempt to extend the controller in this regard in a future post.

Other new features

In the 13.2 release, there is now support for runtime extension of the model. DevExpress is calling this feature custom fields and (again) it is marked as beta. This is not a feature I’ve looked at, but there are a few other non-XAF DevExpress novelties which I’d like to see working within XAF. These include new themes and support for grid batch editing.

One warning

The default directories for the installation have changed again. I’m sure DevExpress has some good reason for this, but if, like me, you have several different versions installed you end up with a confusing directory tree. Whenever this happens I always end up having to modify build scripts and config files so that all my tools work as expected. For those of you who use Red Gate’s .NET Reflector, you can find my config file in this gist.

Conclusions

For the 13.2 release, DevExpress have focused on making the existing functionality work better rather than developing new modules.

Better reports. Better validation. A better framework all round.


Glimpse with DevExpress XAF

$
0
0

I have finally got around to getting Glimpse working with XAF. Glimpse is an amazing extensible ASP.NET plug-in which gives you valuable information about what is going on within your server in production. It’s also very pretty.

Let’s jump right in and have a look at what XAF looks like with Glimpse running.

That banner along the bottom of the screen is the Glimpse heads up display (HUD). Hovering over various sections of it pops up more information:

If you click on the Glimpse icon in the bottom right, you get even more goodies. Here’s the Timeline view.

And there are many other tabs available. The Configuration tab shows the contents of the web.config file. Here’s the Control Tree tab: The Page Life Cycle tab: The Request tab: The Session tab: And the Trace tab including the DevExpress log items that were added to the trace during this page load: As you can see, this is a very valuable glimpse into the server which can be turned on as needed in production.

Adding Glimpse to an XAF application

First install the Glimpse Nuget package into the project.

The Nuget installation will make various incorrect changes to the web.config. The corrected sections are below:

First, add Glimpse to the <configSections>.

web.config
1
2
3
4
5
6
7
8
  <configSections>
    <sectionGroup name="devExpress">
      <section name="compression" requirePermission="false" type="DevExpress.Web.ASPxClasses.CompressionConfigurationSection, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
      <section name="themes" type="DevExpress.Web.ASPxClasses.ThemesConfigurationSection, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
      <section name="settings" type="DevExpress.Web.ASPxClasses.SettingsConfigurationSection, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
    </sectionGroup>
+   <section name="glimpse" type="Glimpse.Core.Configuration.Section, Glimpse.Core" />
  </configSections>

Next, <system.webserver> should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  <system.webServer>
    <handlers>
      <add name="TestControls.axd_*" path="TestControls.axd" verb="*" type="DevExpress.ExpressApp.Web.TestScripts.TestScriptsManager, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, culture=neutral, PublicKeyToken=b88d1754d700e49a" preCondition="integratedMode" />
      <add name="ImageResource.axd_*" path="ImageResource.axd" verb="*" type="DevExpress.ExpressApp.Web.ImageResourceHttpHandler, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, culture=neutral, PublicKeyToken=b88d1754d700e49a" preCondition="integratedMode" />
      <add name="SessionKeepAliveReconnectHttpHandler" verb="*" path="SessionKeepAliveReconnect.aspx*" type="DevExpress.ExpressApp.Web.SessionKeepAliveReconnectHttpHandler, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, culture=neutral, PublicKeyToken=b88d1754d700e49a" preCondition="integratedMode" />
      <add name="WebWindowTemplateHttpHandler" verb="*" path="*.aspx" type="DevExpress.ExpressApp.Web.WebWindowTemplateHttpHandler, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, culture=neutral, PublicKeyToken=b88d1754d700e49a" preCondition="integratedMode" />
      <add name="ASPxUploadProgressHandler" verb="GET,POST" path="ASPxUploadProgressHandlerPage.ashx" type="DevExpress.Web.ASPxUploadControl.ASPxUploadProgressHttpHandler, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" preCondition="integratedMode" />
      <add name="ReportExportResource.axd_*" preCondition="integratedMode" verb="*" path="ReportExportResource.axd" type="DevExpress.ExpressApp.Reports.Web.ReportExportHttpHandler, DevExpress.ExpressApp.Reports.Web.v13.2, Version=13.2.5.0, culture=neutral, PublicKeyToken=b88d1754d700e49a" />
 +    <add name="Glimpse" path="glimpse.axd" verb="GET" type="Glimpse.AspNet.HttpHandler, Glimpse.AspNet" preCondition="integratedMode" />
    </handlers>
    <validation validateIntegratedModeConfiguration="false" />
    <modules>
      <add name="ASPxHttpHandlerModule" type="DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
 +    <add name="Glimpse" type="Glimpse.AspNet.HttpModule, Glimpse.AspNet" preCondition="integratedMode" />
    </modules>
  </system.webServer>

NuGet added some incorrect definitions to system.web. Make sure to restore this section to the DevExpress default:

1
2
3
4
5
  <system.web>
    <httpRuntime requestValidationMode="2.0" />
    <sessionState mode="InProc" timeout="2" />
    <httpHandlers configSource="HttpHandlers.Web.Config" />
    <httpModules configSource="HttpModules.Web.Config" />

And now we’ll add the corrected changes to HttpHandlers.Web.Config and HttpModule.Web.Config

HttpHandlers.Web.Config
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<httpHandlers>
+ <add verb="GET" path="glimpse.axd" type="Glimpse.AspNet.HttpHandler, Glimpse.AspNet" />
  <add verb="*" path="TestControls.axd" type="DevExpress.ExpressApp.Web.TestScripts.TestScriptsManager, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add verb="*" path="ImageResource.axd" type="DevExpress.ExpressApp.Web.ImageResourceHttpHandler, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add verb="*" path="SessionKeepAliveReconnect.aspx*" type="DevExpress.ExpressApp.Web.SessionKeepAliveReconnectHttpHandler, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add verb="*" path="*.aspx" type="DevExpress.ExpressApp.Web.WebWindowTemplateHttpHandler, DevExpress.ExpressApp.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add verb="GET,POST" path="ASPxUploadProgressHandlerPage.ashx" validate="false" type="DevExpress.Web.ASPxUploadControl.ASPxUploadProgressHttpHandler, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add verb="*" path="ReportExportResource.axd" type="DevExpress.ExpressApp.Reports.Web.ReportExportHttpHandler, DevExpress.ExpressApp.Reports.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
</httpHandlers>
HttpModules.Web.Config
1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<httpModules>
+ <add name="Glimpse" type="Glimpse.AspNet.HttpModule, Glimpse.AspNet" />
  <add name="ASPxHttpHandlerModule" type="DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule, DevExpress.Web.v13.2, Version=13.2.5.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
</httpModules>

Next, there is an outstanding problem with the current release version of the Glimpse ASP.NET NuGet package (1.6.0) which prevents it from working with the development webserver. (Apparently it’s fixed in 1.7.0 which should be released soon). If you try to run MainDemo.Web you will get the following error:

Type 'System.Web.HttpContextWrapper' in assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as serializable.

However it works fine against IIS Express (or full IIS). Go to the MainDemo.Web and change the debug properties as follows:

Then run the web application. Glimpse is off by default. In order to turn it on, you need to set a cookie, which you can do by navigating to Glimpse.axd. This is the Glimpse configuration screen.

Additional Glimpse extensions

Glimpse is extensible and there are many additional plugins available. Many of these take the form of additional tabs with details about a particular aspect of your application: SQL Queries, Elmah errors, ASP.NET caching, logging, dependency injection, etc.

MainDemo Sample Project

I have uploaded a modified XAF MainDemo to this GitHub repository with all the above changes. (Don’t forget to debug against IIS Express).

Provisioning a new development machine with BoxStarter

$
0
0

I’ve been playing around with Boxstarter to configure my entire development environment with hardly any user intervention.

Here are the steps:

  1. Install Windows 8.1 on a new machine.
  2. Login.
  3. Open a command prompt and enter the following.
1
START http://boxstarter.org/package/nr/url?http://bit.ly/1kapDXI

That’s it!

Boxstarter will self-install via ClickOnce, asking for various confirmations and ultimately it will prompt you for your login password. (This gets saved and encrypted to allow for unattended reboots and re-logins during the installation). Then the real magic begins. Boxstarter downloads and installs all your tools and configures your environment, rebooting as necessary. An hour later your full development setup is installed, including Visual Studio 2013, any VS extensions, any other programs and tools, all the browsers you need, all critical Windows updates, etc. You just saved yourself a couple of days of work and a lot of hassle.

How does Boxstarter know what to install? There’s a Powershell script located at that bitly address. Let’s take a look at the script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Boxstarter options
$Boxstarter.RebootOk=$true # Allow reboots?
$Boxstarter.NoPassword=$false # Is this a machine with no login password?
$Boxstarter.AutoLogin=$true # Save my password securely and auto-login after a reboot

# Basic setup
Update-ExecutionPolicy Unrestricted
Set-ExplorerOptions -showHidenFilesFoldersDrives -showProtectedOSFiles -showFileExtensions
Enable-RemoteDesktop
Disable-InternetExplorerESC
Disable-UAC
Set-TaskbarSmall

if (Test-PendingReboot) { Invoke-Reboot }

# Update Windows and reboot if necessary
Install-WindowsUpdate -AcceptEula
if (Test-PendingReboot) { Invoke-Reboot }

# Install Visual Studio 2013 Professional 
cinstm VisualStudio2013Professional -InstallArguments WebTools
if (Test-PendingReboot) { Invoke-Reboot }

# Visual Studio SDK required for PoshTools extension
cinstm VS2013SDK
if (Test-PendingReboot) { Invoke-Reboot }

cinstm DotNet3.5 # Not automatically installed with VS 2013. Includes .NET 2.0. Uses Windows Features to install.
if (Test-PendingReboot) { Invoke-Reboot }

# VS extensions
Install-ChocolateyVsixPackage PowerShellTools http://visualstudiogallery.msdn.microsoft.com/c9eb3ba8-0c59-4944-9a62-6eee37294597/file/112013/6/PowerShellTools.vsix
Install-ChocolateyVsixPackage WebEssentials2013 http://visualstudiogallery.msdn.microsoft.com/56633663-6799-41d7-9df7-0f2a504ca361/file/105627/31/WebEssentials2013.vsix
Install-ChocolateyVsixPackage T4Toolbox http://visualstudiogallery.msdn.microsoft.com/791817a4-eb9a-4000-9c85-972cc60fd5aa/file/116854/1/T4Toolbox.12.vsix
Install-ChocolateyVsixPackage StopOnFirstBuildError http://visualstudiogallery.msdn.microsoft.com/91aaa139-5d3c-43a7-b39f-369196a84fa5/file/44205/3/StopOnFirstBuildError.vsix

# AWS Toolkit is now an MSI available here http://sdk-for-net.amazonwebservices.com/latest/AWSToolsAndSDKForNet.msi (no chocolatey package as of FEB 2014)
# Install-ChocolateyVsixPackage AwsToolkit http://visualstudiogallery.msdn.microsoft.com/175787af-a563-4306-957b-686b4ee9b497

#Other dev tools
cinstm fiddler4
cinstm beyondcompare
cinstm ProcExp #cinstm sysinternals
cinstm NugetPackageExplorer
cinstm windbg
cinstm Devbox-Clink
cinstm TortoiseHg
#cinstm VisualHG # Chocolatey package is corrupt as of Feb 2014 
cinstm linqpad4
cinstm TestDriven.Net
cinstm ncrunch2.vs2013

#Browsers
cinstm googlechrome
cinstm firefox

#Other essential tools
cinstm 7zip
cinstm adobereader
cinstm javaruntime

#cinst Microsoft-Hyper-V-All -source windowsFeatures
cinst IIS-WebServerRole -source windowsfeatures
cinst IIS-HttpCompressionDynamic -source windowsfeatures
cinst IIS-ManagementScriptingTools -source windowsfeatures
cinst IIS-WindowsAuthentication -source windowsfeatures

Install-ChocolateyPinnedTaskBarItem "$($Boxstarter.programFiles86)\Google\Chrome\Application\chrome.exe"
Install-ChocolateyPinnedTaskBarItem "$($Boxstarter.programFiles86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe"

Boxstarter works with Chocolatey and you can install anything with a Chocolatey package very easily. As you can see, most of the lines begin with cinstm which is a shortcut for install with Chocolatey if missing. You will notice there are also commands for configuring Windows and IIS options. There is plenty of additional information on the Boxstarter documentation.

What about DevExpress?

Want to install your registered CodeRush and DexExpress components? Easy. Since the installation packages are not available on chocolatey, you will have to put them on a network share accessible from the newly provisioned machine.

Then add the following to your boxstarter script:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Set the following to the network location of the DevExpress installers
$pathToDevExpressComponentsSetup = "\\somewhere\DevExpressComponents-13.2.7.exe"
$pathToDevExpressComponentsSetup = "\\somewhere\DevExpressCodeRush-13.2.7.exe"

# Command line options for unattended installation
# (/EULA is not required for versions earlier than 10.2.10)
$silentArgs = "/Q /EMAIL:myaddress@company.com /CUSTOMERID:A1111 /PASSWORD:MYPASSWORD /DEBUG /EULA:accept"

# Install .NET Components
Install-ChocolateyInstallPackage "DevExpressComponents_13.2" "EXE" $silentArgs $pathToDevExpressComponentsSetup

# Install CodeRush
Install-ChocolateyInstallPackage "DevExpressCodeRush_13.2" "EXE" $silentArgs $pathToDevExpressCodeRushSetup

Warning! don’t put your DevExpress passwords on a public website.

There are plenty of other ways of launching Boxstarter. You can install Boxstarter via Chocolatey. You can run Boxstarter remotely. If you are putting passwords in the installation script, you should choose one of the other options.

Advantages

  • I save time!
  • I can now version control the exact development environment along with the source code!
  • Onboarding new and junior developers is much quicker.
  • In a pinch, I can use this method to quickly provision a development machine with Azure or Amazon Web Services

One Last Hiccup

While I was testing my BoxStarter script I used the Windows 8.1 image from http://www.modern.ie/. Much later in the process I realised that Modern.IE only supplies 32-bit images and the chocolatey installer for Visual Studio extensions currently works only with 64-bit Windows.

A web UI performance tip for XAF web applications

$
0
0

The purpose of this post is to raise your awareness of a toggle which exists in the DevExpress XAF framework which can significantly improve UI performance in the web application.

The biggest XAF project I work with has one very complex business object. The layout for this screen includes about 100 properties, several nested tabs, some custom editors, several collection properties and a whole lot of Conditional Appearance rules. It was very sluggish to navigate - it was taking several seconds to load the detail view and then it was very slow switching between tabs. Switching to edit mode was also slow.

Last week, I almost accidentally changed the value of DelayedViewItemsInitialization to false and noticed that the UI speed was much much better. In fact the general responsiveness of the entire web-application seems much better.

In order to give it a whirl, navigate to the WebApplication.cs file (normally in the ApplicationCode subfolder of your web project) and modify the constructor as follows:

1
2
3
4
public MainDemoWebApplication() {
    InitializeComponent();
    this.DelayedViewItemsInitialization = false;
}

Certainly this is not without consequences, and I would urge a careful reading of the relevant documentation. To be honest, I still don’t really understand why my detail view is so much slower without this change. I have tried to isolate the cause without much success and I will update this post if I find anything new. But if some of your detail views seem overly slow, certainly try it out.

ELMAH with DevExpress XAF

$
0
0

ELMAH (Error Logging Modules and Handlers) is an open source library for logging unhandled exceptions. This post explains how to get it running with the DevExpress XAF main demo.

A couple of amazing facts about ELMAH.

  • It has been around since 2004!
  • It was written by Atif Aziz who happens to be an old school-friend from the International School of Geneva.

XAF provides quite extensive error handling options out of the box, but I have found Elmah better suited to production environments because of the ability to remotely view the full error log.

Setting up

First, get the ELMAH package via NuGet into the MainDemo.Web project. ELMAH provides dozens of different methods of persisting the error log. For this example we’ll choose one of the simplest. Make sure you select the ELMAH on XML Log package.

NuGet makes several automatic modifications to the web.config. Unfortunately, these are not quite accurate enough for XAF. The changes you need to make are detailed below:

Add a <configSection> for ELMAH as alongside the existing devExpress one.

web.config
1
2
3
4
5
6
7
8
9
  <configSections>
    <sectionGroup name="devExpress">...</sectionGroup> <!-- this should already exist-->
    <sectionGroup name="elmah"> <!-- this is new-->
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
     </sectionGroup>
  </configSections>

Your <system.webServer> section should look like this:

web.config
1
2
3
4
5
6
7
8
9
10
  <system.webServer>
    <handlers>...</handlers> <!-- This is unchanged -->
    <validation validateIntegratedModeConfiguration="false" />
    <modules>
      <add name="ASPxHttpHandlerModule" type="DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule, DevExpress.Web.v14.1, Version=14.1.7.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
  </system.webServer>

Add a <location> for the path elmah.axd (alongside the existing <location> tags).

web.config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  <location path="elmah.axd" inheritInChildApplications="false">
    <system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>
      <!-- 
        See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
        more information on using ASP.NET authorization securing ELMAH.

      <authorization>
        <allow roles="admin" />
        <deny users="*" />  
      </authorization>
      -->
    </system.web>
    <system.webServer>
      <handlers>
        <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
      </handlers>
    </system.webServer>
  </location>

Add a new <elmah> section. I put mine just before the final </configuration> tag.

web.config
1
2
3
4
5
6
7
8
  <elmah>
    <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/Elmah.Errors" />
    <!--
        See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for 
        more information on remote access and securing ELMAH.
    -->
    <security allowRemoteAccess="false" />
  </elmah>

Now modify HttpModules.Web.Config to look like this:

HttpModules.Web.Config
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<httpModules>
  <add name="ASPxHttpHandlerModule" type="DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule, DevExpress.Web.v14.1, Version=14.1.7.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
  <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
  <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
  <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
</httpModules>

Now we need to extend XAF’s standard error handling. Create a new class in the web application.

1
2
3
4
5
6
7
8
9
10
public class ElmahErrorHandling : ErrorHandling
{
    protected override void LogException(ErrorInfo errorInfo)
    {
        base.LogException(errorInfo);

        if (errorInfo.Exception != null)
            Elmah.ErrorSignal.FromCurrentContext().Raise(errorInfo.Exception);
    }
}

And then modify Global.asax.cs to instantiate the new class

1
2
3
4
5
6
7
    protected void Application_Start(object sender, EventArgs e) {
        ErrorHandling.Instance = new ElmahErrorHandling(); // <---this line is new
        ASPxWebControl.CallbackError += new EventHandler(Application_Error);
#if DEBUG
        TestScriptsManager.EasyTestEnabled = true;
#endif
    }

The complete files are available with the source code.

Now run the application and trigger an unhandled exception. Change the URL to something that does not exist. Or open any detail view and modify the URL so that the Guid in the ShortcutObjectKey is invalid (replace a digit with an ‘X’). Then the application error page appears.

Then return to the application and change the URL to Elmah.axd. You are looking at the log of all unhandled exceptions.

And for each exception, you can view the full details of any logged exception including coloured stack trace and full server variables.

ELMAH options

By default, ELMAH is configured to disallow remote access to the error logs - only a local user can get to elmah.axd. If you take care of the security implications it can be very useful to enable remote access and monitor the logs on your production servers.

We chose to use an XML file for each error but ELMAH is entirely pluggable. There are dozens of alternatives for persisting the error log including Sql Server, an RSS feeds, to Twitter, even to an iPhone app. There are even third party sites such as elmah.io who will host your error logs for you.

One of the advantages of using XML files is that the files can be copied to another machine. If you look in MainDemo.Web\App_Data\Elmah.Errors, you will find the resulting xml files.

You can just copy these files to another installation’s Elmah.Errors folder and the log will show up when you visit Elmah.axd.

One final note. ELMAH was developed for ASP.NET applications and web services, but it is possible to get it to work with other types of applications such as Windows Forms, Windows Service or console applications. Check out this StackOverflow question.

The source code for this example is on GitHub.

DevExpress 14.2 review

$
0
0

This post is an overview of the brand new version XAF 14.2. The truly outstanding new feature is the ASP.NET report writer which is now available in all XAF applications.

A few months ago, we lost a potential sale because the customer wanted the ability to create custom reports from within the browser. We told them it was impossible to provide a fully-fledged report designer within our web application - but the DevExpress guys have done it! And how!

The web-based report designer

Let’s fire up the MainDemo application and navigate to the reports view. The first thing to notice is that there is a new action Show Report Designer.

The designer action is disabled because the selected report is predefined. Predefined reports are a feature of Reports v2 which were introduced in version 13.2 (see my previous review). So first, we clone the existing predefined report. I renamed the copy (via the edit button) so that we can tell them apart.

Now the Show Report Designer action is enabled. Let’s click it. Whoa! That’s one impressive user interface for a web application!

Let’s add a chart and a few controls. I thought (incorrectly) that the link to the domain model might be somewhat lacking because the report designer is not designed specifically for XAF (you can also use it with non-XAF ASP.NET or ASP.NET MVC applications) but navigating the available domain objects to select a property seemed very natural and simple.

I had a few little mouse issues while trying to resize or move controls, and there were a couple of places where the interface seemed slightly sluggish, but these were very minor issues. In general the designer is slick and easy to use. I also had a little difficulty finding the Save button, but here it is:

And here’s the live output after my modifications.

You can also start from scratch with a new blank report.

This report designer is an extremely impressive achievement. I played around with it for over an hour and it did not crash once. I managed to implement everything I tried including a chart, a bar code and a new data field.

There are some features missing from the web-based report designer compared to the Windows Forms version. Most significant is the ability to attach events and scripts to controls. Here is a full feature comparison table.

I had a quick look for the tools they used to implement it. It looks like it uses jQuery, jQuery.UI and knockout.js and you can automatically bundle the required libraries via a new setting in the web.config. There is some more information here.

On the whole I am utterly impressed. Hats off to the DevExpress team!

Other new features in XAF 14.2

The new 14.2 includes several other new features. These include the ability to store user settings in the data store as well as improvements to the speed of the grids. For a full list of the new features and improvements see here and here.

Fixing slow debugging of ASP.NET applications

$
0
0

For a while I’ve noticed an annoying slowness when debugging ASP.NET applications from Visual Studio. Just after every page load it takes about a second before the buttons become clickable. I noticed mostly when debugging XAF applications, perhaps because the pages are quite complex.

Turns out the culprit is something called Browser Link which was introduced in Visual Studio 2013. It’s enabled by default.

To turn it off you can turn it off from the menu:

Or you can add the following to your web.config file.

1
2
3
<appSettings>
  <add key="vs:EnableBrowserLink" value="false"/>
</appSettings>

Force a complete garbage collection in an ASP.NET application.

$
0
0

How can I force a full garbage collection easily within an ASP.NET application? The method here is for XAF web applications but the same approach should work with any ASP.NET app.

First up: Never mess with the .NET garbage collector.

I sometimes mess with the garbage collector in .NET when I’m trying to pin down some memory problem. Also, after a load test, I prefer to force the garbage collector to collect everything it can so that I can check that the memory drops as expected.

Garbage collection in .NET is complex and it is hard to be sure you’ve done it correctly. This ancient article by Tess Ferrandez pointed me in the following direction.

1
2
3
4
5
6
7
8
9
void ForceGarbageCollection()
{
    /// This will garbage collect all generations (including large object), 
    GC.Collect(3);
    /// then execute any finalizers
    GC.WaitForPendingFinalizers();
    /// and then garbage collect again to take care of all the objects that had finalizers.            
    GC.Collect(3);
}

How can we easily trigger this routine in an XAF web application? First, add the following to the Global.asax.cs file:

Global.asax.cs
1
2
3
4
5
6
7
8
protected void Application_BeginRequest(object sender, EventArgs e) {
    var shouldForceGarbageCollection = Request.QueryString["ForceGC"] != null;
    if (shouldForceGarbageCollection)
    {
        ForceGarbageCollection();
        Response.Write("<H1>Forced garbage collection OK!</H1>");
    }
}

Then whenever you want to trigger a garbage collection, visit the following URL:

http://.../Login.aspx?ForceGC=1

You should see the text ‘Forced garbage collection OK’ in the top left of the browser window.


Migrating a large web application from XAF 12.1 to 15.1 - Part 1

$
0
0

I am the principal software architect for a treasury application in use by over 100 large multinational corporates. Upgrades are generally met with reluctance in the enterprise world and so we’ve been somewhat stuck on an old version of the expressAppFramework.

Recently I’ve been pushing to move to the newer version and I have spent about three weeks migrating the substantial code base to XAF 15.1 and .NET 4.6.

The steps are:

  • Run the project converter tool.
  • Try to compile. Identify the errors which are easily fixable (check with the ‘breaking changes’ documents from DevExpress.)
  • When in doubt, compare the libraries with a decompiler like .NET Reflector.
  • Refactor where necessary (ensure you have unit tests for the behaviour you are changing).

On this last point, my whole approach to refactoring has been shaped by Michael C. Feather’s book Working Effectively with Legacy Code. Highly recommended for anyone maintaining complex applications regardless of whether the code is legacy or not…

I was pleasantly surprised that I was very quickly able to get everything to compile and even run. The layout was not correct, but a lot of things worked straight away.

I then had to spend some time restoring the customisations we’d made to the default ASP.NET layout. In XAF 12.1 these were applied directly to default.aspx.cs and dialog.aspx.cs, but in 15.1 these no longer exist. Instead, you can customise layout by providing your own alternate templates. I was expecting this to be much harder, but by following the instructions in the documentation I managed to restore our layout quite easily.

I still had a lot of failing unit tests. One seemingly minor change to XAF validation proved to be a lot of work to fix in our code. Since 13.1, the XAF Validator class now requires an IObjectSpace parameter in the constructor. This was by far the biggest problem to fix and is the subject of the next blog post.

DevExpress 2015.2 review part 1

$
0
0

Last week, DevExpress released 2015.2.3, their second major version of the year.

There are already some good blog posts about the changes:

Rather than repeat general overviews provided in these, this two-part blog post is more of a ‘deep dive’. In particular I’ll be looking at two of the new features in the expressApp Framework (XAF). Today I’ll cover the new XML serialisation in the report designer. Tomorrow’s post will examine the new batch editing features.

Reports

Reporting is definitely one of the areas where XAF has progressed the most in recent versions. There is now an in-browser report designer (since 14.2) and an alternative implementation of the reports (reports v2, since 13.2).

In this release, I was a little worried that the support for Reports v1 would be deprecated, since our production system has over 100 custom reports and we have not yet looked for an easy way to migrate these to v2. I’m happy to report that v1 reports are still very much present.

However there are also a number of new features in v2 Reports, not least of which is the ability to store the report’s layout in XML. There’s currently not a lot of documentation about the XML serialization, so let’s dig in and see what we can discover.

XML serialization deep dive

First I ran the demo and ran the Copy Predefined Report on the Contacts Report.

Then I ran the report designer and added a dummy OnBeforePrint() script to one of the table’s cells.

1
2
3
private void xrTableCell8_BeforePrint(object sender, System.Drawing.Printing.PrintEventArgs e) {
  var x = 23;
}

Then, in the MainDemo’s Updater.cs file, I placed the following code.

1
2
3
4
5
6
7
var reportData = ObjectSpace.FindObject<ReportDataV2>(new BinaryOperator("DisplayName", "Contacts Report") & new BinaryOperator("IsPredefined", new OperandValue(false)));
if (reportData != null)
{
    var report = ReportDataProvider.ReportsStorage.LoadReport(reportData);
    report.SaveLayoutToXml(@"C:\Temp\ContactsReport.xml");
    report.SaveLayout(@"C:\Temp\ContactsReport.repx");
}

This creates two output files. One of them contains the familiar .repx format. The other contains the newer .xml serialization.

The contents of the XML file is displayed below and is 77 lines long.

By comparison, the ContactReport.repx is 408 lines long and much harder to read.

As you can see the XML file is much smaller and simpler than the .repx file. At first I didn’t believe it contained all the necessary information, so I started up the MainDemo WinForms application, created a blank new report and imported the layout and the layout looks correct and the preview loads with data as expected.

Scripts

What about scripts? Are they serialized in the xml version? You bet. In the XML export you can see this has been serialised near the top of the file.

1
2
3
ScriptsSource=
   "&#xD;&#xA;private void xrTableCell8_BeforePrint(object sender, System.Drawing.Printing.PrintEventArgs e)
   {&#xD;&#xA;  var x = 23;&#xD;&#xA;}&#xD;&#xA;"

Setup

In order to use this new XML serialization of report layouts, you must set the ReportsV2 module to use it.

1
reportsModuleV2.ReportStoreMode = ReportStoreModes.XML

This will most likely invalidate any reports which have already been serialized to the database. There are some notes on how to fix this here.

Default for new projects

What about new projects? I created a new solution and chose DevExpress 15.2 XAF Solution Wizard as the type of solution and added the reports module. Now when I navigate to the WinApplication.Designer.cs file, I find:

1
2
3
4
5
//
// reportsModuleV2
//
this.reportsModuleV2.ReportDataType = typeof(DevExpress.Persistent.BaseImpl.ReportDataV2);
this.reportsModuleV2.ReportStoreMode = DevExpress.ExpressApp.ReportsV2.ReportStoreModes.XML;

So the default storage method for new projects is now XML.

Conclusion

The XML serialisation looks like a considerable upgrade to the mechanism for storing, loading and saving reports. Now I just need to find a good way of converting my existing v1 reports… Perhaps a future blog post.

Coming up

Tomorrow I’ll be looking at the new batch editing in grids in more detail.

DevExpress 2015.2 review part 2

$
0
0

This is the second and final dive into some of the new DevExpress XAF 2015.2 features. The first part covers the Report Designer and the new XML serialisation.

Batch editing

Another feature I’m excited about is the support for batch editing within the web application grids.

Let’s see what happens when combined with the validation rules. What happens if I edit two rows but only one has a validation problem - does the whole batch get rejected? Or just the row with the problem?

First I modified the edit mode of the Tasks grid to Batch via the model.

Then I added a new RuleRequiredField validation rule via the model.

I started up the MainDemo application and navigated to the Tasks view and tried to delete the subject of multiple rows at the same time.

I never even got to press Save because of another new 15.2 feature: inplace validation! The rules are being validated without a round trip to the server!

So let’s try another way. Inplace Validation does not work with all rule types, so I deleted my RuleRequiredField from the model and instead added a new RuleFromBoolProperty to the DemoTask object as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
[MemberDesignTimeVisibility(false)]
[RuleFromBoolProperty("SubjectIsRequired", 
    DefaultContexts.Save, 
    "Subject is required.", 
    UsedProperties = "Subject", 
    SkipNullOrEmptyValues = false)]
public bool IsSubjectRequired
{
    get
    {
        return !String.IsNullOrWhiteSpace(Subject);
    }
}

Now we go back to the list view for Task and try to set multiple subjects to empty.

Fantastic! This time the broken rules appear for each row and it is quite clear which message belongs to which object.

So the batch editing makes use of inplace validation when it can, but handles more complex validation rules well too. This is an excellent combination because the inpalce validation will help to make the client seem very quick and responsive.

Conclusion

This concludes my review of DevExpress 2015.2. We’ve looked in some detail at two of the most impressive new features - the way reports are serialised and the improvements to client-side validation. These changes help with performance and ease of maintenance and I’m very happy to see that the DevExpress team has focused on these areas. Looking forward to the 2016 releases!

An XAF workaround for 'Prevent this page from creating additional dialogs.'

$
0
0

Both Chrome and Firefox have a ‘feature’ which allows a user to ignore future calls to confirm(). Once this has been checked, any subsequent calls to confirm() return false immediately without showing the window.

In XAF, this behaviour prevents the application from working correctly. For instance, it becomes impossible to confirm a deletion.

The following controller provides a work around. It injects some javascript into the page wrapping the call to confirm(). The new code detects when the Prevent this page from creating additional dialogs checkbox has been checked and returns true instead. The confirmation window still does not appear, but the XAF application works as if the user had pressed confirm instead of cancel.

# HandleDisabledConfirmationsController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Web;
using DevExpress.Web.Internal;

namespace NetModule.Web.Controllers
{
    public class HandleDisabledConfirmationsController : Controller
    {
        public static bool IsChrome
        {
            get
            {
                return RenderUtils.Browser.IsChrome;
            }
        }

        public static bool IsFirefox
        {
            get
            {
                return RenderUtils.Browser.IsFirefox;
            }
        }

        protected override void OnFrameAssigned()
        {
            base.OnFrameAssigned();
            if (IsChrome || IsFirefox)
            {
                WebWindow window = Frame as WebWindow;
                if (window != null)
                {
                    window.CustomRegisterTemplateDependentScripts += window_CustomRegisterTemplateDependentScripts;
                }
            }
        }

        private void window_CustomRegisterTemplateDependentScripts(object sender, CustomRegisterTemplateDependentScriptsEventArgs e)
        {
            // wrapper for 'confirm'
            WebWindow window = (WebWindow)sender;

            // Detect the user has checked the 'prevent this page from creating additional dialogs'.
            // In which case assume all confirmations are accepted, rather than the default rejected
            var confirmWrapper = @"
<script>
window.nativeConfirm = window.confirm;
window.confirm = function(message)
{
    var timeBefore = new Date();
    var confirmBool = nativeConfirm(message);
    var timeAfter = new Date();
    if ((timeAfter - timeBefore) < 350)
    {
        confirmBool = true;
    }
    return confirmBool;
}
</script>";

            e.Page.ClientScript.RegisterClientScriptBlock(GetType(), "WrapConfirmations", confirmWrapper);
        }

        protected override void OnDeactivated()
        {
            if (IsChrome || IsFirefox)
            {
                WebWindow window = Frame as WebWindow;
                if (window != null)
                {
                    window.CustomRegisterTemplateDependentScripts -= window_CustomRegisterTemplateDependentScripts;
                }
                base.OnDeactivated();
            }
        }
    }
}

The controller works by timing the milliseconds to close the confirmation window. If it is less than 350 milliseconds we can assume the confirmation window never opened owing to the checkbox. In this scenario, it returns true (confirm) rather than false (cancel) in order for XAF to function correctly.

XAF Goes Mobile: CTP Version Ships with v15.2.9

Proof of Concept with DevExpress XAF Mobile

$
0
0
Some words upfront:

DevExpress XAF Framework is one of our .net Dev Tools with it we creates lot of our Business Apps in the last couple of years. 

Now DevExpress was understand his customer needs to support also the Mobile Devices. 

The mobile apps will reuse the database, as well as certain aspects of the data model, application logic and UI settings of our existing XAF applications.

More Information on XAF :(https://www.devexpress.com/Products/NET/Application_Framework/)

More Information on XAF Mobile :
(https://sqlnetcon.blogspot.de/2016/06/xaf-goes-mobile-ctp-version-ships-with.html)


Why i wrote this Post: 
We need to test the new XAF Mobile Solutions for our LOB Apps 
and try to find out how we use it, and how fast end efficient is the tool to meet our customer Requirement for Mobile Apps. 


let's start:  

Download the latest Version of DevExpress from Download Center 
in my Case: 

  • eXpressApp Framework v15.2.9 ( currently i use 16.1. Beta )
  • Visual Studio 2015  ( it works from VS2012ff ) 
  • XAF Data Service Visual Studio Template Download it here :( C# / VB.net
  • ASP.NET MVC 4 ( that's installed in VS2012 ff - in case not :  ( you can download it here )  

we use our Yoga Classroom Schedule Reservation System. 
This App is used for Yoga or Training Studios to handle Scheduling, Customer Communication, Contract Administration and Invoicing.   

Here is the Sample Screen Shot




That's the Web UI. 

The Mobile App needs to following functions: A Student needs to check the next Events, Contract Details and Invoice/Payments. Event Reservation and Canceling must be possible. 

back to VS Solution and add the DataService to the Project.






common pitfall: 
after adding the DataService you need to recompile one Time the Project to update the NUGet Packages. Do nothing else odd .. ( a coffee is possible ) 

now change the Application Name in new MobileApplication.cs

in my Case: pncYCRS







now let's add the Module Classes

if you miss the Model Class in Tool Box, validate the framework Settings in DataService Project in my Case all Projects running on  ( Default for DataService is 4.0.0.0 but the DevExpress Default for new Projects is > 4.0.0.0  so compare and set it all the same.   





now we can add the Module to DataService Project:




after this Step the Module and all Reference Modules are loaded.

now open the web.config in DataService Project and change the 

connectionString="" to your SQL Server and Database.

DataServcice is ready. 

Now go back to the Mobile App and change Security, Views and Navigation.


MobileApplication Security:

In our App we use AuthenticationStandard so we need to modify the 

MobileApplication.cs file 

   protected override void OnSetLogonParametersForUIBuilder()
        {
           
            // If your application requires authentication before building a mobile application UI, 
            // create a test user in DatabaseUpdater and provide these test user credentials through SecuritySystem.LogonParameters here.
            AuthenticationStandardLogonParameters logonParameters = (AuthenticationStandardLogonParameters)SecuritySystem.LogonParameters;
            logonParameters.UserName = "Admin";
            logonParameters.Password = "";

            //base.OnSetLogonParametersForUIBuilder();

        }
[edit:] is only needed for Simulator to run an App with Authentication

add the EventHandler for OnSetLogonParametersForUIBuilder()

in My PoC i use a very Secure way : Admin without Password ;-)  ( don't do it in PROD ;-)


MobileApplication Model Editor Views:
[edit:] in Mobile Application open the Model Editor
you need to modify the Property: Mobile visible = true 
the Views with the 




don't forget to flat the Navigation, 
currently no SubItems are supported:


MobileApplication Model Editor Navigation:



save, compile 

and Run the MobileApplication in VS Studio  ( Start DEBUG ( Default Port is 62695 ) 

if the Browser is open, open a new TAB and paste the App Simulator URL 

http://maindemo.cloudapp.net/Simulator/?appPage=/player.cshtml?app=http://localhost:62695/MetadataService.svc/AppConfig?dataServiceUrl=http://localhost:62695/DataService.svc 

and run the App Simulator: 

... so and drums .... ( the VS Studio is in Debug Mode at this Time  ) 




so a Student can now ( after the very secure Login ) set a seat in a Class Room ..




Work Hours spent on this effort:
4 hours  : Programming for DataService and configuration
12 hours Model Design View Column shaping, 
Details Page Shaping to prepare the Mobile UX / UI.

well done DevExpress !! I love it :-)  

my Next Steps 
  • use the production Code base 
  • add Mobile App there 
  • deploy the Mobile App to App Store
  • prepare the PROD Environment with Load Balancer and 2 Web Servers
this point will be new Posts in next Days.

so enjoy 
Cheers Lars 








Smart hiding of the selection boxes in XAF web applications

$
0
0

When an XAF list view has no selection-based actions available, the selection box still appears in the grid. Users get confused. In this post, we’ll look at a workaround.

The problem

In the XAF MainDemo, lets make Departments read-only for the User role.

Updater.cs
1
2
3
userRole.AddTypePermissionsRecursively<Department>(SecurityOperations.Create, SecurityPermissionState.Deny);
userRole.AddTypePermissionsRecursively<Department>(SecurityOperations.Write, SecurityPermissionState.Deny);
userRole.AddTypePermissionsRecursively<Department>(SecurityOperations.Delete, SecurityPermissionState.Deny);

Then start the web application, login as John and navigate to the Departments list view. There is a column selection box, but it serves no purpose. There are no actions that depend on a grid selection.

Without the SelectionColumnVisibilityController

The fix

Here is a controller which calculates whether there are any available actions which require one or more rows to be selected. If there are none, the selection box will not appear.

Add the following controller to the MainDemo.Module.Web project. It hides the selection box if there are no actions which depend on a grid selection.

SelectionColumnVisibilityController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
using System;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.SystemModule;
using DevExpress.Web;
using System.Linq;

namespace MainDemo.Module.Web.Controllers
{
    public class SelectionColumnVisibilityController : ViewController
    {
        public SelectionColumnVisibilityController()
        {
            TargetViewType = ViewType.ListView;
        }

        private bool IsSelectionColumnVisible()
        {
            bool isSelectionColumnRequired = false;
            // remove checkbox if there are no available actions
            foreach (Controller controller in Frame.Controllers)
            {
                if (!controller.Active)
                    continue;

                if (controller.Actions.Count == 0)
                    continue;

                bool allowEdit = true;
                if ((Frame is NestedFrame) && (((NestedFrame)Frame).ViewItem is PropertyEditor))
                    allowEdit = (bool)((PropertyEditor)((NestedFrame)Frame).ViewItem).AllowEdit;

                foreach (ActionBase action in controller.Actions)
                {
                    if (action.SelectionDependencyType == SelectionDependencyType.RequireMultipleObjects)
                    {
                        if (action.Active || IsActionInactiveBySelectionContext(action))
                        {
                            if (action.Enabled || IsActionDisabledBySelectionContext(action))
                            {
                                isSelectionColumnRequired = true;
                                break;
                            }
                        }
                    }
                }
                if (isSelectionColumnRequired)
                    break;
            }
            return isSelectionColumnRequired;
        }

        private bool IsActionInactiveBySelectionContext(ActionBase action)
        {
            if (action.Active)
                return true;
            else
            {
                foreach (string item in action.Active.GetKeys())
                {
                    if (item == ActionBase.RequireMultipleObjectsContext || item == ActionBase.RequireSingleObjectContext)
                        continue;
                    if (!action.Active[item])
                        return false;
                }
                return true;
            }
        }

        private bool IsActionDisabledBySelectionContext(ActionBase action)
        {
            if (action.Enabled)
                return true;
            else
            {
                foreach (string item in action.Enabled.GetKeys())
                {
                    if (item == ActionBase.RequireMultipleObjectsContext ||
                        item == ActionBase.RequireSingleObjectContext ||
                        item == ActionsCriteriaViewController.EnabledByCriteriaKey)
                        continue;
                    if (!action.Enabled[item])
                        return false;
                }
                return true;
            }
        }

        protected override void OnViewControlsCreated()
        {
            base.OnViewControlsCreated();
            ASPxGridView grid = ((ListView)this.View).Editor.Control as ASPxGridView;
            if (grid != null)
            {
                grid.Load += grid_Load;
                grid.DataBound += grid_DataBound;
            }
        }

        protected override void OnDeactivated()
        {
            base.OnDeactivated();
            ASPxGridView grid = ((ListView)this.View).Editor.Control as ASPxGridView;
            if (grid != null)
            {
                grid.DataBound -= grid_DataBound;
                grid.Load -= grid_Load;
            }
        }

        void grid_Load(object sender, EventArgs e)
        {
            SetSelectionColumnVisibility(sender, e);
        }

        void grid_DataBound(object sender, EventArgs e)
        {
            SetSelectionColumnVisibility(sender, e);
        }

        private void SetSelectionColumnVisibility(object sender, EventArgs e)
        {
            bool isSelectionColumnVisible = IsSelectionColumnVisible();
            if (!isSelectionColumnVisible)
            {
                var grid = (ASPxGridView)sender;
                var selectionBoxColumn =
                    grid.Columns
                        .OfType<GridViewCommandColumn>()
                        .Where(x => x.ShowSelectCheckbox)
                        .FirstOrDefault();

                if (selectionBoxColumn != null)
                {
                    selectionBoxColumn.Visible = false;
                }
            }
        }
    }
}

Run the application again and see the difference. Now the grid looks like this. Notice, there is no longer a selection box on the row.

By the way, this is how it looks with old-style XAF web apps.

Without the SelectionColumnVisibilityController

With the SelectionColumnVisibilityController


No Visual Studio designers for generic ViewController versions

$
0
0
XAF Controllers are technically descendants of the System.ComponentModel.Component class, primarily to be smoothly integrated with the Visual Studio design time features. The standard Component designer does not support generic classes due to a Visual Studio limitation: https://stackoverflow.com/questions/6877217/can-visual-studio-designer-show-classes-inheriting-generic-types 
This is a Microsoft functionality and we cannot do much about it on our side. So, if you have a class like this, and try to double click it or use the Enter key for it in the Solution Explorer, you will receive this error, which is expected here:



This has always caused support calls to us from day one and still causes them today. Check out this support article to learn more about this situation and our previous recommendations.

Starting with v17.2.6, we have removed the visual designer association from the base ViewController<ViewType> and ObjectViewController<ViewType, ObjectType> classes - they will always be opened directly in the Code Editor now. This should work fine in Visual Studio 2015, 2017 and newer versions. This does not work for Visual Studio 2013 and older versions and we decided not to investigate this further, because of the very low usage rate of these IDEs nowadays.



We believe that this new behavior is most expected here and really hope that it will not cause new questions😀. Originally, generic View Controllers were specially designed for users who prefer to do everything manually and want to have a more elegant and compact code. So, opening the Code Editor for such classes is naturally expected. Taking this opportunity, we also improved our online documentation to further promote generic View Controllers, since most of our Support Center answers, demos and docs use this concise code approach anyway.

My team and I are looking forward to hear from you on what you think of the new behavior.

Minor improvements to the ConditionalAppearance and ViewVariants modules - XAF v17.2.6

$
0
0

I also wanted to inform you of some "home work" done by us in v17.2.6 to improve your XAF development experience with these modules:

GridListEditor - An appearance rule has no effect in the new item row until a value is entered into any cell
View Variants Module - ListView variant always takes the DataAccessMode value from the root node instead of own model differences, which may cause an exception due to an incompatible ListEditor type

FreeImages.com/linusb4



While the first scenario is quite specific (we heard about it from our largest XAF customer and a few others), the second one was likely encountered if you use the ViewVariants module. For instance, when having one variant as a grid operating in server mode while having another variant as tree, pivot, scheduler or whatever ListView editor that does not support server mode. In the past, this unsupported configuration could cause an exception at runtime. Starting with v17.2.6, it all works as the majority our users expect - at least we believe so based on the feedback we received. The online documentation on the ViewVariants module will be updated accordingly by release time, of course. Finally, many thanks to Dave Hesketh, Martin Praxmarer, our MVPs, as well as other customers for bringing this to our attention.

If you cannot wait for the next maintenance update (v17.2.6) to test both things, install this night buildDevExpressNETComponents-17.2.exe


As always, my team and I are looking forward to hearing what you think, even on such small things and maybe others that could make your life a bit easier.

Beware of the failures when uploading JPEG/JPG images using the System.Drawing.Image type properties - XAF ASP.NET WebForms v17.2.5

$
0
0
 issue, which is already fixed in XAF v17.2.6

We apologize for the inconvenience, and if you cannot wait for the hotfix or did not plan to spend additional time on upgrade right now, please consider using the workarounds.

Important notes and workarounds
From v14.2, Image Property Editors (both WinForms and ASP.NET) support images saved as byte array objects (in addition to previously supported System.Drawing.Image objects). This allows us to significantly decrease web server memory consumption because the byte array can contain an image in a compressed format and the server does not have to unpack it. The previous approach with the System.Drawing.Image type property is still supported, but we recommend that you migrate to the byte array storage to improve performance. Refer to the BLOB Image Properties in XPO  and How to change the image property type from System.Drawing.Image to byte array help topics for more details. 



Customization Forms and Model Editor Improvements - XAF v17.2.6

$
0
0
Customizing Views layout, e.g., making certain fields visible, changing their locations is something that XAF developers usually learn and appreciate from day one. That is because you can drastically change the UI of your app by just moving the mouse and without any coding and recompilation, also at runtime. 

The next maintenance release (v17.2.6) should come with a few minor improvements to the customization forms in DetailView, ListView and the Model Editor, which we hope will be welcomed by both developers and end-users as well.



  1. The customization forms will preserve their size and position. In the Model Editor, the settings will be stored permanently either in the Application Model (runtime) or in the *.SUO file (design time). The same form at runtime will maintain its settings during application life time only. This small difference is intentional: it should still be convenient to make several extensive customizations at once, but will certainly avoid any possible confusion and support calls from end-users saving and forgetting their forms in some unexpected place/state. This must work fine with multiple monitors, tabbed MDI and SDI, multiple root and nested Views (last closed form settings win). Anyway, should you notice anything unexpected here during your tests, please let us know.
  2. The customization forms will have the search box enabled by default. In real world applications with 100-200 items it should greatly save time on searching a required item.
  3. The customization forms will handle the Up/Down Arrow and Delete keys to quickly navigate or remove items in the main list. Again, it must save time in complex apps.
  4. The last item added from the Object Model dialog will be focused in all the customization forms. We experimented with this a few months ago and decided to enable this helpful function everywhere.
  5. Finally and not related to the customization forms, we have enabled the behavior shown in this blog post by default. Note that here we intentionally did not add another grouping level by namespace, not to complicate access, but we welcome to your thoughts on it.

As always, my team and I welcome your feedback on these improvements if you want to test the nightly 17.2 build: DevExpressNETComponents-17.2.exe. This test build does NOT yet include certain functionalities for the PivotChart, TreeListEditor and PivotGridListEditor, so test them with the GridListEdior and DetailView's layout for now. Should you have problems with the customization forms or anything else, we will be more than happy to listen and help.

Learning through reading the source code

$
0
0
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. 




When you focus an external library API whose source code you do not own, and press F12, Visual Studio will first show a boring confirmation dialog (BTW, can it be avoided at all?):



and then the actual code for you to learn:

Viewing a decompiled definition
Check out the the official docs for more details.

I am paying attention to this primarily because in my opinion, it is difficult to become a good  developer quickly without reading various source code (this is not specific to XAF itself), and this new function helps on this long path. 

If we talk about XAF specifically, then for certain advanced tasks like creating complex and powerful custom XAF UI elements (e.g., compared to the default ones by covered scenarios), researching and debugging the XAF source code is strongly advised for better efficiency or troubleshooting. That's because the XAF source code of standard List and Property Editors, Actions Containers, Templates and other UI elements contain dozens of perfect real-world examples and best practices to learn besides often simpler code examples in demos, support knowledge base and online documentation. Just look at "c:\Program Files (x86)\DevExpress 17.1\Components\Sources\DevExpress.ExpressApp\DevExpress.ExpressApp.Web\Editors\ASPx\" and other folders and you will see what I mean.

Practically, these examples should help you better understand how things work under the hood, remember more and more good patterns, compare  your code with the best practices and spot errors, missing standard scenarios in your own implementation. For those who forgot, the DevExpress source code can also be installed by our installation, so no external tools are required to inspect it (a valid license is required, though): 

Debugging the source code is also part of the same learning process and is no less important in my opinion. For this, there is a standard approach with symbols can be helpful: 
How can I debug DevExpress .NET source code using PDB files. It is also no secret that there are alternative options with paid or free tools like dotPeek that can be helpful for external libraries code inspection and debugging as well. What I am describing may look too advanced for an average XAF developer, but believe me this is not rocket science and mastering those techniques can really save you a lot of time and nerves.

See Also:
eXpressApp Framework > Concepts > Debugging, Testing and Error Handling

Usability change for SingleChoiceAction with a single visible sub-item - YOUR FEEDBACK IS NEEDED!!!

$
0
0

We have incorporated the usability improvement from my past post in v18.1 by default. It affects SingleChoiceAction with ShowItemsOnlyClick = False and ItemType = ItemIsOperation  and should improve UX for the majority of our users. We also want to enable the same behavior in an upcoming minor update (e.g., v17.2.6 or next). Our team would appreciate your feedback to help us decide on the latter.




Questions/Risks

While this behavior is always desired for the New & Clone Actions, because their meaning is clear from the context, certain custom and built-in Actions like Show In Report, Change State may be affected when they keep only a single sub-item. Examples:

17.2.5 and older versions:




18.1 (and 17.2.6+, if the majority likes it):


Notice that we've replaced a dropdown/triangle glyph with a simple button. We believe that this visual change is still not breaking (and thus is acceptable for a minor version), and it all is more logical than it was before. Before, when ShowItemsOnClick = False, you could still press the SingleChoiceAction's button blindly, i.e., without expanding its items using the triangle glyph. To help you avoid mistakes, the button is now displayed in the ChoiceActionItem's tooltip. You can also control the default Action item behavior using the DefaultItemMode property. If you do not like the new behavior, you can always set ShowItemsOnClick = True in the Model Editor.

Question: Are you OK to have this new behavior as default in v17.2.6+ too? 

Please answer with +1 or -1 with comments in this post. If you need to test your real apps live before making a decision, I can provide you with v17.2 or v18.1 hotfix builds. Thanks for your help in advance.

AccessViolationException at Microsoft.VisualStudio.Shell.Interop.IVsRegisterEditors.UnregisterEditor may occur in Visual Studio 2010, 2012

$
0
0

This sporadic design time error might go away in Visual Studio 2013, 2015, 2017 and newer.

Exception

Type:
 System.AccessViolationException
Message:
 Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
StackTrace:
   Microsoft.VisualStudio.Shell.Interop.IVsRegisterEditors.UnregisterEditor(UInt32 dwCookie)
   Microsoft.VisualStudio.Shell.Package.Dispose(Boolean disposing)
   DevExpress.ExpressApp.Design.Core.XAFPackageV4.Dispose(Boolean disposing)
   Microsoft.VisualStudio.Shell.Package.Microsoft.VisualStudio.Shell.Interop.IVsPackage.Close()



We have been tracking this error for years from XAF users using https://logify.devexpress.com. We also received a few tickets, but could never replicate this behavior for debugging. Our attempts to log diagnostic information about this error did not help either. Well, it does not seem to relate to the DevExpress.ExpressApp.Design.Core.XAFPackageV4.Dispose method itself. There are also many non-XAF reports devoted to the same error, but different packages on the Web. https://www.google.com/search?q=Microsoft.VisualStudio.Shell.Interop.IVsRegisterEditors.UnregisterEditor+accessviolationexception

From the Logify stats, we know that this error occurs only with these two old Visual Studio versions. It seems that something got fixed in newer IDE versions. If you are on Visual Studio 2010 or 2012 and encountered this error at design time, consider upgrading your IDE - it's 2018 after all:-)








Diagnostic tool for Security System - YOUR FEEDBACK IS NEEDED!!!

$
0
0
Earlier we described the prototype of diagnostic tool. We created it to help a developer to understand why access to a certain object and its members is allowed or denied.  
  
We analyzed the feedback we got on this matter and provided the following improvements in v17.2.6:
 - To enable the security diagnostic tool, use the familiar EnableDiagnosticActions option.
 - You can select a type and a member you wish to analyze.
 - Results are presented in the XML format.
 - We provide more details on how the diagnostic tool calculates results.
 - There is a way to visualize criteria for easier analysis.

You can get more info about the tool from the KB article below:

                                     https://www.devexpress.com/kb=T589182 

Your feedback is needed
Please test the tool and let us know whether it is useful in your development process and how you would change it. For instance, the more specific there are use cases of this tool where it was difficult to configure permissions for a user, the greater it is possible that we can arrive at technical solutions that will address them.


XAF WinForms v17.2.6 - Beware of missing icons in inactive tabs after restoring the Tabbed MDI layout

$
0
0
I want to draw your attention to the T611846: DocumentManager - TabbedView does not show images for documents issue already fixed in the XtraBars Suite and which XAF WinForms Tabbed MDI users might encounter after upgrading to v17.2.6. The number of users who already hit this exceeded a certain threshold, and the known issues section in the What's New documentation or manual searching the Support Center may not help effectively discover it yourself. So, I am posting this here to widely inform those who have not yet upgraded their XAF/WinForms projects to v17.2.6.

You can also download a hotfix build from here.

Please accept sincerely apologies from our XAF and XtraBars teams for all the inconvenience here.


Integrating UnitOfWork and XPObjectSpace descendants into an XAF app

$
0
0

We've made some code changes for v17.2.6+ as well as created two articles for advanced XAF developers who may need the subject for some low-level tuning:





Sub-classing is itself easy.  The most interesting part comes for the two popular security configurations: the Integrated  mode and the Middle-Tier Application Server. Thankfully, this is rarely required for complex or specific scenarios only.  Here are several customer tickets for your reference:

    How to map a property to a calculated database column (implement a read-only persistent property)
    xpo and sql server identity fields
    Using inherited UnitOfWork object
    XAF: Create CreateCustomObjectSpaceProvider with parameters from login window
    SecurityStrategyComplex: How to modify objects/properties in code when the user does not have the permission?

To learn more about low-level options to control how your application saves data and where, check out the How to customize the underlying database provider options and data access behavior in XAF.

Your feedback is needed!
Finally, I am just curious: if you search your entire solution in Visual Studio (Control+Shift+F) for any of the ": UnitOfWork", ": XPObjectSpace", ": XPObjectSpaceProvider" strings or their VB.NET equivalents (e.g., Inherits UnitOfWork), how many occurrences  would you have and for what? Please let me know in the comments!

FreeImages.com/Terry Eaton

Old entries in the XPObjectType table - YOUR FEEDBACK IS NEEDED!

$
0
0

I am reviewing the priority of a quite dated SC item on the subject and wanted to ask the community for help. The problem may occur when old business class libraries exist in the application folder. By default, XPO tries to load them by the XPObjectType info along with the new versions. This may lead to a conflict at runtime or startup performance degradation. In the latter case, assembly type resolution may come at a cost (see the point 3.5 under How to measure and improve the application's performance).

Why are we hesitating to remove old XPObjectType entries by default?
1. Removing old XPObjectType records affects other apps accessing this database and is serious. Outdated service table records may relate to business class records too (inheritance mapping is in use very often). So, deleting them will lead to foreign key constraint violation. Creating a sophisticated generic solution for this is not easy task.
2.  XPObjectType table updates are safe only with the full database and application knowledge. Users possessing this knowledge can use ready options like the ModuleUpdater.UpdateXPObjectType method (SQL Server-specific) shown in the How to: Handle Renamings and Deletions of Business Classes and their Properties article. We also demonstrated a db-server agnostic updater in the aforementioned ticket.
3. We help XAF developers detect this fact by writing log messages. Check out your eXpressAppFramework.log file for the "Resolve the 'DevExpress.ExpressApp.Workflow.v11.1' assembly" and similar messages referring to outdated DevExpress assemblies. Of course, a developer or DBA can preview the XPObjectType table using database engine tools. 
4. Old application assemblies living within the new application folder is unexpected in itself. At least, I would instinctively always prefer a fresh Windows install over an update. Even though we are aware of some specific scenarios (e.g., with plugins or locked assemblies), it is still easy to install the new application version into the new folder. This natural action will avoid these risky circumstances completely.
5. Finally, we heard less or almost nothing about this behavior lately. Most likely, this is because an XAF app stores user settings in the database and not in a file system by default and thus the point #4 is simpler.

Your feedback counts!
Regardless of the reasoning above, I want to learn better on how it looks from your side. Please answer two simple questions here in comments or use the https://www.devexpress.com/ask service:

Q1. Did you ever notice negative effects from the outdated type and assembly references in the XPObjectType table?

Q2. If so, describe your problems and use-case scenarios in greater detail. Also, comment on their frequency (e.g., once, every year, etc.) as well as on your current solution implementation and maintenance costs, if any.

In advance, thank you very much for your help! Hopefully, I am not looking this way from your side😜

FreeImages.com/Richard Dudley

Updates to the new Rich Text Editor Module and ASPxGridLookup-based PropertyEditor in v18.1

$
0
0
Please do not miss the updates to the following KB articles from our team:

https://www.devexpress.com/go/XAF_Try_RichTextEditorModule_v18.1.aspx
We have moved further and added Mail Merge support to the new module. To enable this feature, set the MailMergeDataType property in RichEditWindowsFormsModule in Application Designer. Now it's also possible to work with multiple rich text editors in the Ribbon mode. You can open the content of the selected rich text editor in a popup window using the 'Show in Popup' context menu item. Also now you can view rich text content in a list view's columns.

https://www.devexpress.com/go/XAF_Try_ASPxGridLookupPropertyEditor_v18.1.aspx
ASPxGridLookupPropertyEditors provides the capability for creating/modifying objects via a popup window. The add and edit buttons open the popup window to create a new object or to modify a selected one. ClearButton allows clearing the editor value. 

Your feedback counts!
If you are an active Universal subscriber and would like to test these new features prior to the official release, download the DevExpress XAF v18.1 Preview installer. By providing early build access, we hope to find out whether our new features and solutions address your requirements and issues. Your testing efforts and your feedback help us deliver the best possible final implementation.


Capturing a user's signature in an XAF mobile application - UPDATE for v17.2.8+

$
0
0
Starting with v17.2.8, MobilePropertyEditor supports the Edit ViewEditMode. We updated our How to use a Signature Pad in XAF Mobile example and enabled SignatureAction in the Edit mode accordingly. This improvement saves end-users time not only for this particular scenario (they no longer require to switch views), but also when working with regular image properties.

FreeImages.com/Carl Dwyer

To see the difference in action, refer to the GIF file below and compare it with the one from my previous post:


Your feedback is needed!
For early testing, we published a daily build. You can download it using the following link: DevExpressNETComponents-17.2.7.18099.exe. We look forward to hearing from you.

XAF v18.1 - An application will not start if there are non-existent member or type info for the Application Model elements

$
0
0
Starting with v18.1, we have introduced checks for type and member info existence at the core level. If a type or member is not existent, the exception will be thrown by default:


NOTE: Type and member info is mandatory as ALL standard and third-party modules rely on it. We do NOT recommend disabling this check in production, because your application will still fail later (you may not notice it and it will happen on an end-user machine).

Typical but not all scenarios where you may see this error are:
- Code changes to an underlying object type structure. For instance, class renaming or removing, namespace or assembly changing; member renaming, removing or its underlying type changing (also applied to custom fields added at runtime).
- Invalid model differences for changed types and members are still present (e.g., for ListView columns and a DetailView layout, appearance and validation rules).

Please refer to this Support Center article for more details on possible solutions and ways to disable this behavior.

The future of old RichTextPropertyEditor and HtmlPropertyEditor with the introduction of the new Office-inspired Word Processing Module - YOUR FEEDBACK IS NEEDED!!!

$
0
0

Why bothering?
As many of you already heard, we are shipping the Office module with the Rich Text Editor in v18.1. It allows end-users to create, load, change, print, save and convert documents in different formats, including HTML. At this stage, this new module is for WinForms only. We see ways to support more Office functionality and cover other platforms in the future.

As you know, there are two very old XAF Property Editors, which perform similar functions, but with lower quality. They are both based on standard Microsoft controls:
DevExpress.ExpressApp.HtmlPropertyEditor.Win.HtmlPropertyEditor - this is a part of HTML Property Editor Module that integrates the System.Windows.Forms.WebBrowser component. 
DevExpress.ExpressApp.Win.Editors.RichTextPropertyEditor - this is a part of the standard DevExpress.ExpressApp.Win module that integrates the System.Windows.Forms.RichTextBox component.

We created them almost 10 years ago when DevExpress did not provide controls with similar functions. Today, their usage is not justified also because of missing High DPI support, nice icons and useful functions like Mail Merge. Workarounds can be implemented for these standard controls too, but it does not make much sense to invest our resources into this when much better options are already available. These editors have not recently received updates in other areas anyway. This is still a maintenance work for our team that could have been spent on something else.

What we want to change and how?
1. We want to hide the DevExpress.ExpressApp.Win.Editors.RichTextPropertyEditor and its related APIs from Intellisense, the FeatureCenter demo and also from the Model Editor. This is all not to confuse new and existing users. Most Support Center tickets on it are from early days when users could not integrate our RichEditControl on their own with E1509 and the like.
If a string property was decorated with the [EditorAlias(EditorAliases.RichTextPropertyEditor)] attribute and no Office module is added, it will function as before for existing users. If the Office module is added into the app, its new PropertyEditor will be used instead. Since the EditorAliasAttribute feature has been documented only in 2017, the aforementioned declaration had almost zero usage according to our Support Center stats.
If the DevExpress.ExpressApp.Win.Editors.RichTextPropertyEditor was set in PropertyEditorType in the Model Editor, everything will continue to function as before, even with the new Office module plugged in.
If you test Beta v18.1, you may see that our new PropertyEditor is called "RichEditPropertyEditor". We also want to rename it to RichTextPropertyEditor, because we screwed up with the naming. Since the DevExpress.ExpressApp.Win.Editors.RichTextPropertyEditor will be hidden from everywhere, new and existing users should not be confused by the same name.
2. We want to stop recommending DevExpress.ExpressApp.HtmlPropertyEditor.Win.HtmlPropertyEditor for new projects, remove it from demos and mark as "obsolete" in learning materials like we did for Reports Module (Obsolete). Instead, we want to recommend our customers use the new Property Editor from the Office module (it also supports HTML content through the DocumentStorageFormat setting). For instance, in our MainDemo app, you can make this Model Editor setting for the Note class (after Beta v18.1 the full type name will likely change): 

  <BOModel>
    <Class Name="DevExpress.Persistent.BaseImpl.Note">
      <OwnMembers>
        <Member Name="Text" PropertyEditorType="DevExpress.ExpressApp.Office.RichEdit.Win.RichEditPropertyEditor" DocumentStorageFormat="Html" />
      </OwnMembers>
    </Class>
  </BOModel>

If you do not want to migrate to the new editor, you can continue using the former one as you wish -  no problem. Also, it was thus far not critical for our team to think about the removal date at this stage, but we will see how it goes in the future. Anyway, we do not expect any further development to this editor by the reasons I described above. The same notes and procedures apply here as for our Reports (Obsolete) module.

Your feedback is needed!
This plan is NOT YET realized, but I wish to do so by v18.1 release time (in 2-3 weeks). This is the right time to avoid changes when more customers use the new Office module in their projects. If you support this initiative, we will describe the migration path in the Breaking Changes section of our What's New docs, as always. This should help improve user experience for many XAF users and save our team resources.


I'd like to hear from you on what you think about this. Specifically:
Q1: Are you using DevExpress.ExpressApp.Win.Editors.RichTextPropertyEditor and DevExpress.ExpressApp.HtmlPropertyEditor.Win.HtmlPropertyEditor?
Q2: If so, what do you like about them, what problems did you experience so far and how important are they for you?
Q3: What do you think about the proposed changes and the migration path? How would you handle this yourself?


FreeImages.com/Jeff Prieb

UPDATE on Customization Forms - XAF WinForms v18.1.3+

$
0
0
With v18.1.3 (RTM or Beta2), a tooltip will be shown for complex property paths to avoid confusion from fields with the same name. Refer to the attached video:



Note that since multiple customization forms can be opened at the same time, now the form's header also includes the View's caption. We hope your end-users will appreciate these minor usability improvements.

See Also:
Customization Forms and Model Editor Improvements - XAF v17.2.6

Update on WinForms SVG Images Support in v18.1

$
0
0
Please do not miss our recent update in the WinForms SVG Images Support - Preview in v18.1 KB article:

We've extended the number of scenarios where SVG images are now correctly shown in a WinForms XAF application with the following:
● SVG images used for SingleChoiceAction itself and its items shown in the bar and ribbon controls.


● SVG images used for SingleChoiceAction shown in the popup menu.



● SVG images used for ParametrizedAction shown in the bar and ribbon controls and in the popup menu.



● SVG images used for ParametrizedAction shown in ButtonsContainer on a detail view.



● SVG images used for nodes in TreeListEditor via the new DevExpress.Persistent.Base.General.ITreeNodeSvgImageProvider interface.



Your feedback is needed!
If you want to test this feature with your real world applications on a virtual machine (or another suitable test environment) prior to the official release, download the v18.1 preview build here: DevExpress XAF v18.1 Preview installer 

SVG images in RichTextPropertyEditor

$
0
0
We've temporarily disabled SVG icons in RichTextPropertyEditor (a part of the new Office module in v18.1.3) in an XAF app until we redraw all default PNG images (by the end of 2018). Our designers aimed to avoid bad UX after mixing PNG and SVG images within one screen. For instance, the merged main and RichEditControl's menus may look inconsistent. Different images in independent UI contexts are acceptable, e.g., SVG in a separate report designer and PNG in the main form.

To force SVG images, set the static DevExpress.ExpressApp.Office.Win.OfficeWindowsFormsModule.ForcePngIcons property to False in the Main method of the YourSolutionName.Win/Program.xx file (available in v18.1.3+).



To check our progress with regard to SVG icons support for WinForms and to help future development by answering questions from our team or sharing early feedback, please visit this knowledge base article.

Quick questions on our change log

$
0
0
Q1: How often do you check the "Resolved Issues", "Known Issues", "Breaking Changes" pages in our What's New documentation? For instance: "never, did not know about this page"; "every minor"; "every major"; "rarely, only when I encounter an issue"; "once 1-2 year when we upgrade"; your option.
Q2: What would you like to improve about these pages to make your work more comfortable?
Q3: Are you missing anything important from the "New Major Features" section like this for v18.1?



This post idea came after looking at the fixed XAF issues for the upcoming v17.2.8:

As you can see, titles have different formatting and it is not easy to find all issues for a certain platform or module, e.g. Mobile.

This is how a bit better formatted list looks now:

This needs a little bit more discipline from our team, but may be more helpful for our customers. I am  still hesitating because this problem may not exist for many, because Google Analytics reports low visits for these pages.

I hope you can help us see whether to focus on more important things by answering my questions above. Your other thoughts and suggestions are welcome too, as always.

See Also: General Information > Installation > Upgrade Notes

Improved diagnostics for ListView errors in Server Mode

$
0
0
From the Core - ListView shows blank rows or freezes in certain scenarios in Server Mode ticket:


In certain scenarios, a list view with DataAccessMode set to Server may show empty rows or operate slowly. This behavior is usually caused by exceptions thrown during data loading that are handled internally. Such situations can be diagnosed using the debugger (see What to do when the grid displays blank rows in Server Mode and ASPxGridView displays empty rows when bound via Server Mode).

Starting with v18.1, we improved the XAF built-in diagnostics for errors occurring in Server Mode data sources. Now, server-side errors (intercepted via the ServerExceptionThrown event) are written to the application log. In addition, when the log level is set to 4 (Verbose), errors reported via the InconsistencyDetected event are also written to the log. While the latter event doesn't always indicate a problem, the error message may be helpful in researching the cause of performance issues.



Web - How to avoid issues with data-bound controls due to missing or non-unique key values

$
0
0
Check out the Web - How to avoid issues with data-bound controls due to missing or non-unique key values KB article to make sure that your business model is aligned with the best practices. Here is a short extract:


Symptoms
You may notice the following side-effects in a Web ListView while it may work fine in WinForms:
- A wrong DetailView may be selected when an unsaved record is clicked in ListView;
- ListView may have no selection column;
- In inline edit or batch mode, all records may become selected or editable.
These effects are especially frequent in a non-persistent class ListView or a nested ListView for an aggregated details collection with newly added or unsaved records.

Explanation
All new records added to the ListView and not yet saved to the database will be assigned with a temporary key value (e.g. -1 or an empty Guid/String value depending on your key type). In a Web application, ListView uses a business class key property to identify records, because there is no way to access real business objects from the web browser. Hence, there is a requirement that each business object must have a unique value assigned to the key property. ASPxGridView and other data-bound controls require this for the correct operation of such features like selection, filtering, sorting, grouping and the standard XAF functionality that depends on them (see also ASPxGridBase > KeyFieldName). 
If a key property is missing in a business class completely or if a key property is not initialized with a unique value immediately after the record is created, these duplicate or invalid records may lead to a data-bound control malfunction as expected.


Please inform me of your health-check results!

FreeImages.Com


WinForms - How to manage long text in DocumentManager tabs

$
0
0
Please check out the Problem and Solutions sections in the T629226: WinForms - Long text is cut off in DocumentManager tabs ticket.

We are going to improve this in the upcoming v18.1+. Our designer's favorite is #2, but we cannot implement it in a minor version due to a breaking change. BTW, option #1 shown in the screenshot below also looks fine. Take note of solution #3 as well - you can customize this behavior without writing any code.


Please let me know what option you prefer or if you are handling it differently. Thanks.

BTW, have you modernized your XAF WinForms apps using SVG icons and Light Style in v18.1? Click the screenshot below to see the new look & feel. Do you like it?


How to access and manipulate a custom ASP.NET user control from a Controller in a Module project

$
0
0

We have updated our How to: Show a Custom Data-Bound Control in an XAF View (ASP.NET) tutorial with more information on how to pass external data to custom Web user controls

Previously, our customers had the following difficulties with this task:

- In which project to create web user controls. By default, ASP.NET suggests doing this in the YourSolution.Web project. Adding an *.ASCX control into the YourSolutionName.Module.Web project led to errors.

- How to deal with circular references when attempting to reference the YourSolution.Web project from a Controller defined in the YourSolutionName.Module.Web project.
- How to pass an IObjectSpace, XafApplication or current View object to a custom user control.


A common way to resolve such a dependency (unrelated to XAF itself) is to define an interface within your class library (a XAF module project) and implement it by using your custom user control.


Concurrent Record Updates Merging in XPO-based XAF ASP.NET apps (v18.1)

$
0
0
From the Optimistic Concurrency Control help topic:
"In multi-user data-aware applications, there are times when one user edits a record while another user is editing the same record. As long as the changes being made do not affect the same field (e.g., one user modifies the task description while another user attaches a file), XAF applications can resolve the situation, and merge simultaneous changes made by different users. To enable field-level locking, set the XpoDefault.TrackPropertiesModifications static property to true."

Previously, this feature was available only for WinForms apps. With v18.1, ASP.NET WebForms XAF applications can handle concurrent record updates by merging simultaneous changes (by two or more different users) as well:


Here is also a video from our MainDemo.Web app (run from the Demo Center):



Notice that two different users can edit different properties (e.g., NickName and SpouseName) and then merge  these non-conflicting changes.

Have you already enabled this feature in your XAF Web apps? Please let us know how this works for you.

WinForms v18.1.3 - Beware of the NullReferenceException in property grids on mouse scroll

$
0
0
In XAF, users of the Model Editor and Report Designer tools are likely to be affected by this issue. Well, advanced users who enabled the ShowPropertyGrid property in the layout control or made custom PropertyGridControl integrations may be affected too - at least we all know a few😉

Our XtraVerticalGrid developers are aware of this problem and have already fixed it in v18.1.4 (it should be out in 1.5-2 weeks). In the meantime, the hotfix is already available for download at PropertyGridControl - The NullReferenceException is thrown when scrolling and an editor is active. Please accept sincerely apologies from our XAF, XtraReports and XtraVerticalGrid teams for all the inconvenience here.

Here is the error callstack for your reference:

System.NullReferenceException at DevExpress.XtraVerticalGrid.PGEditingState.MouseWheel(DevExpress.Utils.MouseWheelScrollClientArgs) at DevExpress.XtraVerticalGrid.BaseHandler.DevExpress.Utils.IMouseWheelScrollClient.OnMouseWheel(DevExpress.Utils.MouseWheelScrollClientArgs) at DevExpress.Utils.MouseWheelScrollHelper.OnScrollLine(DevExpress.Utils.DXMouseEventArgs, Int32, Boolean) at DevExpress.Utils.MouseWheelScrollHelper.OnMouseWheel(System.Windows.Forms.MouseEventArgs) at DevExpress.XtraVerticalGrid.BaseHandler.OnMouseWheel(System.Windows.Forms.MouseEventArgs) at DevExpress.XtraVerticalGrid.VGridControlBase.OnMouseWheelCore(System.Windows.Forms.MouseEventArgs) at DevExpress.XtraEditors.Container.EditorContainer.OnMouseWheel(System.Windows.Forms.MouseEventArgs) at System.Windows.Forms.Control.WmMouseWheel(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) at DevExpress.XtraEditors.Container.EditorContainer.WndProc(System.Windows.Forms.Message ByRef) at DevExpress.XtraVerticalGrid.VGridControlBase.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr, Int32, IntPtr, IntPtr)


Tip 1: The How to obtain a design-time exception call stack KB article describes how to locate these important error details, especially if your Visual Studio crashed. This information is helpful when submitting to support. BTW, to improve your chances of getting a faster and helpful answer from support, check these related tips as well:
   How do I ask a good question? (StackOverFlow)
   How to Report Bugs Effectively (Simon Tatham)
   A request for simple example programs (Julian Bucknall)
   Collect and Analyze the Diagnostic Information (XAF documentation)


Tip 2: When you located the call stack information and determined from it that an exception is raised from DevExpress code, I recommend you search for one or several topmost call stack method names in our support database. This behavior might be reported by someone else, e.g.: https://www.google.com/search?q=PGEditingState.MouseWheel


FreeImages.com

Scheduled long running tasks with XAF

$
0
0

Today I will detail about implementing Scheduled Long running tasks in an abstract and reusable way.

The Requirement

Usually, I have a business objects that store time depended data e.g. Exceptions, Logs etc. There is a need to periodically clean up those tables.

The parameters

First we need to identify the problem parameters so we can create a model interface. Parameterizing the problem in a model interface is very useful, because the end user can switch it off if something goes wrong.

Parameters will be: the business object type which we want to clear its records, the criterion to filter the objects and the time to execute this action. The interface along with its registration for this is bellow.

using System;
using System.ComponentModel;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Model;
using DevExpress.ExpressApp.Model.Core;

namespace PurgingRule.Module.Controllers{
//rules container
publicinterface IModelClassPurgingRules:IModelClass{
IModelPurgingRules PurgingRules{ get; }
}

[ModelNodesGenerator(typeof (PurgingRulesModelNodesGenerator))]
publicinterface IModelPurgingRules : IModelNode, IModelList<IModelPurgingRule>{
}

publicinterface IModelPurgingRule:IModelNode{
string Criteria{ get; set; }
bool ExecuteOnApplicationStart{ get; set; }
TimeSpan TimeSpan{ get; set; }
[DefaultValue(500)]
int ChunkSize{ get; set; }
}

//will help us generated more rules if needed
publicclass PurgingRulesModelNodesGenerator:ModelNodesGeneratorBase{
protectedoverridevoid GenerateNodesCore(ModelNode node){

}
}

publicclass PurgingController:Controller,IModelExtender{

//model interface registration
publicvoid ExtendModelInterfaces(ModelInterfaceExtenders extenders){
extenders.Add<IModelClass,IModelClassPurgingRules>();
}
}
}

Now, it is possible to use the Model Editor to configure in which object we want to apply our rules.

image

That's nice!

but I like to use an Editor to write the Criteria and need to add a few more bits for this to happen. For this we need to use the CriteriaOptions and Editor attributes as shown:

publicinterface IModelPurgingRule:IModelNode{
[Editor("DevExpress.ExpressApp.Win.Core.ModelEditor.CriteriaModelEditorControl, DevExpress.ExpressApp.Win" + XafAssemblyInfo.VersionSuffix + XafAssemblyInfo.AssemblyNamePostfix, typeof(UITypeEditor))]
[CriteriaOptions("TypeInfo")]
string Criteria{ get; set; }
[Browsable(false)]
[ModelValueCalculator("((IModelClass) Parent.Parent).TypeInfo")]
ITypeInfo TypeInfo { get; }

Now it should be straight forward to construct any criterion with the help of this build-in editor.image

Making Thread Safe Database calls

Working in a multi threaded environment (long running tasks) is build-in in XAF's XPO ORM so we go for it. Just make sure you enable the switch as bellow.

protectedoverridevoid CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args) {
var threadSafe = true //enable threadsafe;
args.ObjectSpaceProviders.Add(new XPObjectSpaceProvider(XPObjectSpaceProvider.GetDataStoreProvider(args.ConnectionString, args.Connection, true), threadSafe));
args.ObjectSpaceProviders.Add(new NonPersistentObjectSpaceProvider(TypesInfo, null));
}

Scheduling the long running tasks

We will use the StartNewPeriodic extension method and will follow the next steps in our code.

  1. Execute once at application startup .
  2. Collect the model rules to execute.
  3. Calculate the next execution time. For this we need to store the last execution time so I used the RuleScheduleStorage BO class found at the end of the next snippet
  4. Periodically schedule parallel calls to the PurgeObjects method for each rule.
    publicclass PurgingController:Controller,IModelExtender{
    protectedoverridevoid OnFrameAssigned(){
    base.OnFrameAssigned();
    //1.execute once at application startup .
    if (Frame.Context == TemplateContext.ApplicationWindow){
    //2.collect the model rules to execute.
    var purgingRules = Application.Model.BOModel.Cast<IModelClassPurgingRules>().SelectMany(rules => rules.PurgingRules ).ToArray();
    IEnumerable<(IModelPurgingRule rule, DateTime executed)> ruleExecutionTimes;
    //create an objectspace to query the last execution time from the database
    using (var objectSpace = Application.CreateObjectSpace(typeof(RuleScheduleStorage))){

    DeleteObsoleteRules(objectSpace, purgingRules);
    var rulesToSchedule = purgingRules.Where(rule =>rule.Interval!=TimeSpan.MinValue);
    //get an enumerable of (IModelPurgingRule rule, DateTime executed)
    ruleExecutionTimes = CalculateExecutionTimes(rulesToSchedule, objectSpace);
    }

    foreach (var ruleExecutionTime in ruleExecutionTimes){
    var timeSinceLastExecution = DateTime.Now.Subtract(ruleExecutionTime.executed);
    //calculate if periodic task should start with a delay based time passed since last execution
    int delay=timeSinceLastExecution<ruleExecutionTime.rule.Interval?(int) ruleExecutionTime.rule.Interval.Subtract(timeSinceLastExecution).TotalMilliseconds:0;
    //starts the task periodically
    Task.Factory.StartNewPeriodic(() => PurgeObjects(ruleExecutionTime.rule),
    interval: (int) ruleExecutionTime.rule.Interval.TotalMilliseconds, delay: delay);
    }
    }
    }

    privatestatic IEnumerable<(IModelPurgingRule rule, DateTime executed)> CalculateExecutionTimes(IEnumerable<IModelPurgingRule> rulesToSchedule, IObjectSpace objectSpace){
    return rulesToSchedule.Select(rule => {
    var ruleScheduleStorage = objectSpace.GetObjectsQuery<RuleScheduleStorage>()
    .FirstOrDefault(storage =>storage.RuleScheduleType == RuleScheduleType.Purging && storage.RuleId ==((ModelNode) rule).Id);
    return (rule:rule,executed:ruleScheduleStorage?.Executed ?? DateTime.MinValue);
    });
    }

    privatevoid DeleteObsoleteRules(IObjectSpace objectSpace, IModelPurgingRule[] purgingRules){
    var ids = purgingRules.Cast<ModelNode>().Select(node => node.Id).ToArray();
    var rulesToDelete = objectSpace.GetObjectsQuery<RuleScheduleStorage>().Where(storage =>
    storage.RuleScheduleType == RuleScheduleType.Purging && !ids.Contains(storage.RuleId)).ToArray();
    objectSpace.Delete(rulesToDelete);
    objectSpace.CommitChanges();
    }

    You might wonder why I inherit from a controller and used the OnFrameAssigned method as start signal and not simply write my code at Application.SetupComplete.Event. The reason for this is that I like to keep my implementation in separate files and not pollute the Module.cs.

    The Long Running PurgeObjects method:

    1. Stores last execution time in the database.
    2. Deletes filtered objects in chunks.
    privatevoid PurgeObjects(IModelPurgingRule purgingRule){
    try{
    Tracing.Tracer.LogVerboseText($"Purging {purgingRule}");
    var objectsCount = 0;
    using (var objectSpace = Application.CreateObjectSpace(purgingRule.TypeInfo.Type)){
    StoreExecutionTime(purgingRule, objectSpace);
    var criteriaOperator = objectSpace.ParseCriteria(purgingRule.Criteria);
    var objects = objectSpace.GetObjects(purgingRule.TypeInfo.Type, criteriaOperator);
    objectSpace.SetTopReturnedObjectsCount(objects, purgingRule.ChunkSize);
    while (objects.Count > 0){
    objectsCount += objects.Count;
    objectSpace.Delete(objects);
    objectSpace.CommitChanges();
    objectSpace.ReloadCollection(objects);
    }
    }
    Tracing.Tracer.LogVerboseText($"Purged {purgingRule}-{objectsCount}");
    }
    catch (Exception e){
    Tracing.Tracer.LogError(e);
    }
    }
    privatestaticvoid StoreExecutionTime(IModelPurgingRule purgingRule, IObjectSpace objectSpace){
    var ruleId = ((ModelNode) purgingRule).Id;
    var ruleScheduleStorage =objectSpace.GetObjectsQuery<RuleScheduleStorage>().FirstOrDefault(storage =>
    storage.RuleScheduleType == RuleScheduleType.Purging && storage.RuleId == ruleId) ?? objectSpace.CreateObject<RuleScheduleStorage>();
    ruleScheduleStorage.RuleScheduleType = RuleScheduleType.Purging;
    ruleScheduleStorage.RuleId = ((ModelNode) purgingRule).Id;
    ruleScheduleStorage.Executed = DateTime.Now;
    objectSpace.CommitChanges();
    }

    Last minute feature

    I am happy with the PurgingRules, but I would be more happy if I could invalidate them based on a CSharp expression. For example I want to have some rules on my dev machine only.

    The PurgingRules already have a criterion so we could create a Custom Function to evaluate CSharp expressions.

    I want to use this code in eXpandFramework which there is a <=.NET4 dependency so for evaluating CSharp expressions I chose compile on the fly + caching the fromExpressionEvaluator.Evalmethod.

    publicclass EvaluateExpressionOperator:ICustomFunctionOperator{
    public List<string> Usings=new List<string>();

    publicconststring OperatorName = "EvaluateExpression";

    publicstatic EvaluateExpressionOperator Instance{ get; } = new EvaluateExpressionOperator();

    public Type ResultType(params Type[] operands){
    returntypeof(object);
    }

    publicobject Evaluate(paramsobject[] operands){
    var csCode = string.Join("",operands);
    var usings = string.Join(Environment.NewLine,Usings);
    var eval = ExpressionEvaluator.Eval(csCode, usings);
    return eval;
    }

    publicstring Name => OperatorName;
    }

    image

    Working with such rich libraries like DevExpress XAF suite you make serious stuff in no time!

    Easy asynchronous web notifications

    $
    0
    0
    Today we will discuss another common case, how to create a progress bar to notify our web users about the state of their long running tasks. For this discussion we will work with  DevExpress XAF Business Application Framework. We will develop ONE SMALL CLASS, we can then copy paste to any XAF project.

    It is a good idea before starting any work to search the Support Center for ideas and ask the DevExpress support guys. Doing so we found many tickets with ready to work solutions. Some of them are good candidates e.g How to start a long running operation in ASP.NET application using the ThreadPool.QueueUserWorkItem method and show its progress in a browser using a WebService method.

    The problem with the previous sample , is that uses a WebService to periodically call back in a Controller. I wanted to create a reusable implementation inside a library and a WebService cannot live in a library.
    But I got the idea on how to proceed. I just need to create a ViewItem to host a ASPxProgressBar and with periodic call-backs I will inject JavaScript code to SetPosition on the client side ASPxClientProgressbar

    Step 1
    First we need a sample project so let’s use the XAF New Solution Wizard to create a new Web Project. We do not need any extra modules or security, just create it as simple as possible. After the solution is created add a very simple Domain Object so XAF can generate a web View for it.

        [DefaultClassOptions]
    publicclass MyObject : BaseObject {
    public MyObject(Session session) : base(session) { }
    }
    Step 2
    In addition we  need create a sequence of Tasks that will return the state of our work. You can use any technology you prefer e.g. webservices, TPL tasksetc. as long as it returns asynchronously it fits our case. For this discussion I will use the System.Reactive library. To create the sequence the next line will be enough.

    Observable
    .Interval(TimeSpan.FromMilliseconds(1000))
    .Subscribe(l => Console.WriteLine($"Task {l} completed on Thread:Environment.CurrentManagedThreadId}"));

    If you want to test how it behaves add it in a console app and you should see the following output.

    image

    Step 3
    In XAF we use Controllers to communicate with our Views so let’s create a very simple Controller and add our sequence and later connect the progress bar.

    publicclass MyController : ViewController<DetailView> {
    public MyController() {
    var action = new SimpleAction(this, "StartLongOperation", PredefinedCategory.Tools);
    action.Execute += action_Execute;
    }

    void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
    Observable.Interval(TimeSpan.FromMilliseconds(1000)).Subscribe();
    }
    }

    This controller declares an StartLongOperationAction and starts our sequence, exactly as described on XAF docs.
    Currently XAF already generated the web UI and actions. Here how the DetailView of MyObject looks like.
    image

    Step 4
    Now its time to create the progress bar container. For this scenario we do not need to alter the state of MyObject  so we will follow the XAF docs on how to create a ViewItem rather than a PropertyEditor. (How to implement a ViewItem). Our starting ViewItem comes next:

    publicinterface IModelProgressViewItem : IModelViewItem {
    }

    [ViewItem(typeof(IModelProgressViewItem))]
    publicclass ProgresViewItem : ViewItem {
    public ASPxProgressBar ProgressBar{ get; privateset; }

    public ProgresViewItem(IModelProgressViewItem info, Type classType)
    : base(classType, info.Id){
    }

    protectedoverrideobject CreateControlCore() {
    ProgressBar = new ASPxProgressBar();
    return ProgressBar;
    }

    }

    We override the CreateControlCore method and just return an ASPxProgressBar component included in DevExpress suite.
    Step 5
    As I mentioned before this sample (How to start a long running operation in ASP.NET application using the ThreadPool.QueueUserWorkItem method and show its progress in a browser using a WebService method) use a Javascript  Timer to periodically call a WebService which communicates with a XAF Controller.

    Our scenario is very similar but we need to remove the dependency to the WebService because we need to push the implementation to our ExcelImporter module that is part of the eXpandFramework and is very hard to host a WebService in a library so it can be reusable and with friction-less installation.

    So let’s introduce the Javascript timer in our ProgressViewItem and use Callbacks to notify the server instead of the WebService used in the SC sample. This is as always well document in the XAF docs (How to: Raise XAF Callbacks from Client-Side Events and Process these Callbacks on Server).

    private XafCallbackManager CallbackManager => ((ICallbackManagerHolder)WebWindow.CurrentRequestPage).CallbackManager;
    publicint PollingInterval{ get; set; }
    publicvoid Start(int maximum){
    var script = CallbackManager.GetScript(_handlerId, $"'{ProgressBar.ClientInstanceName}'","",false);
    ProgressBar.ClientSideEvents.Init =
    $@"function(s,e) {{
    if(window.timer) window.clearInterval(window.timer);
    var controlToUpdate = s;
    window.timer = window.setInterval(function(){{
    var previous = startProgress;startProgress = function () {{ }}; //this line disables the Loading Panel see Q427477 in SC
    {script}startProgress = previous;}},
    {PollingInterval});}}"
    ;
    }
    In short the Start method use the build-in XAF CallBackManager to generate a script with one parameter the ProgressBar.ClientInstance name. We pass this parameter because we may want to use multiple progress-bars in the same view. Next the timer calls this script every PollingInterval.

    Whats left is to implement the IXafCallbackHandleras shown.

    publiclong Position{ get; set; }
    publicvoid ProcessAction(string parameter){
    var script = $"{parameter}.SetPosition('{Position}')";
    WebWindow.CurrentRequestWindow.RegisterStartupScript(_handlerId,script,true);
    }
    Here we just created a script that uses the client side ASPxProgressBar API to SetPosition based on the new ProgressViewItemPosition property.
    Step 6
    To consume the ProgressViewItem we modify the MyController defined in Step 3 like:

    void action_Execute(object sender, SimpleActionExecuteEventArgs e) {
    var progresViewItem = View.GetItems<ProgresViewItem>().First();
    progresViewItem.Start(maximum:100);//Start the timer
    Observable
    .Interval(TimeSpan.FromMilliseconds(1000))
    .Subscribe(l => progresViewItem.Position=l );//Update the position for each Task
    }
    Step 7
    Finally let's add our ProgressViewItem to the a View. We will use the Model editor to create it and drag & drop to the Layout.

    Below you can see how the ProgressViewItem works in runtime in our sample solution.

    I wrote this post as a proof of concept rather than a complete implementation, so I am not posting any samples. However you can download the the complete ProgressViewItem used if  you wish from this gist.
    Below you can verify that it works fine in a real world complex module like the ExcelImporter.

    XAF can do Mobile and Windows as well, a Mobile implementation for this scenario does not make much sense but have a look how it looks in the Windows platform.


    Compile your solution without DevExpress assemblies installed in 4 lines

    $
    0
    0

    Do you have a project, where you use Azure pipelines to build it? How do you get the referenced DevExpress assemblies? In this post I will discuss how we go for it in eXpandFramework. The process however is applicable in all other projects.


    After executing the next 4 lines your solution should compile:


    Install-Module XpandPosh
    Register-PackageSourceProviderName Nuget Name DX_private Location https://nuget.devexpress.com/YOURTOKEN
    Install-DX -binPath YOURBIN -dxSources $(Get-PackageSourceLocations -join ";") -sourcePath YOURSOURCESPATH -dxVersion 18.2.5
    Update-HintPathOutputPath YOURBIN SourcesPath YOURSOURCESPATH

    For feedback drop an issue at https://github.com/eXpandFramework/DevExpress.PackageContent