ZeroSharp

Robert Anderson's ones and zeros

Persisting Changes to Config Files Within NuGet Packages

| Comments

Whenever NuGet updates or restores a NuGet package, the config files within it are overwritten. Here’s a method to make sure the changes are reapplied via a config transform whenever the solution is built.

I’m using the NUnit.Runners NuGet packages. To get our coverage tool to play nicely, I need to replace <supportedRuntime "v2.0.50727"> with <supportedRuntime "v4.0.30319"> within the NUnit-console-x86.exe.config.

Normally, a config transform is for modifying the web.config or app.config files. Here, we need to modify a config file within the packages subdirectory.

In my .csproj file, I have added the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  <PropertyGroup>
   <NUnitRunnerDir>$(SolutionDir)packages\NUnit.Runners.2.6.3\tools\</NUnitRunnerDir>
  <PropertyGroup>

  <!-- Default NUnit test runner requires a modification to the config file-->
  <UsingTask
    TaskName="TransformXml"
    AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

  <Target Name="AfterBuild" Condition="exists('$(NUnitRunnerDir)NUnit-console-x86.exe.config')">
    <TransformXml
        Source="$(NUnitRunnerDir)NUnit-console-x86.exe.config"
        Destination="$(NUnitRunnerDir)NUnit-console-x86.exe.config"
        Transform="$(SolutionDir)UnitTests\Transforms\NUnit-console-x86.exe.CLR4.config" />
  </Target>

And the transform file itself looks like this:

NUnit-console-x86.exe.CLR4.config
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <startup>
    <supportedRuntime version="v4.0.30319" xdt:Transform="Replace" />
  </startup>
</configuration>

By the way, Web.config Transformation Tester is a handy tool!

Now whenever I build the project, the AfterBuild event ensures the supportedRuntime version is set correctly.

Pardon the Interruption

| Comments

I left my laptop on a train a couple of months ago. Good thing I remembered my brain. I’m back now. This blog still lives.

A Web UI Performance Tip for XAF Web Applications

| Comments

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.

Provisioning a New Development Machine With BoxStarter

| Comments

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.

CruiseControl Notifications on Your iPhone

| Comments

CCWatcher is a great iPhone app for any developer who is using CruiseControl.NET, Jenkins or Hudson for their continuous integration.

We have used CruiseControl.NET for several years to automate all builds. The build kicks off automatically whenever we push changes and this frequently happens a few times a day. We aim to have everything green at the end of every day.

A full build with unit tests and functional tests takes about an hour, so often, I will leave the office while the build is still running. But then I wouldn’t know if I’d broken the build.

For a long while I was looking for a better way of being informed of build failures on my phone.

Enter CCWatcher. It’s a simple application which allows you to enter the address of your build server and it will tell you the status of the projects you’ve configured.

Whenever I need to check whether I’ve broken the build, I can just pull to refresh.

When I first discovered the application it had a problem with CruiseControl.NET’s remote services (which we needed because the actual build server is on a network machine not visible from the internet). I contacted SixAfter and was very impressed with the helpful response from Michael Primeaux. Within a couple of weeks there was a new version of the app with support for the remote services configuration. Hats off.

Glimpse With DevExpress XAF

| Comments

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).

DevExpress 13.2 Review - Part 2

| Comments

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.

DevExpress 13.2 Review - Part 1

| Comments

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.

How to Improve XAF Grid Layout for Chrome

| Comments

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

Three Ways to Store a List of Currency Codes in XAF

| Comments

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.