Strongly typed XtraReports

At work, we have an application that needs to generate PDF documents related to invoicing. While I usually dislike working with WYSIWYG editors of any kind, the process of creating PDFs to mail and print is so fixed unit-based that it actually makes some sense here. We ended up using XtraReports from DevExpress.
The problem with most reporting frameworks is that they are, well, reporting solutions instead of pure document generation frameworks. So all their concepts are tightly coupled to directly querying a database and aggregating data. Things I don’t need here: I just want strongly typed bindings on in-memory objects! Out of the box, I don’t see any simple way to achieve this. This means: let’s get to work! 🙂
 
Here’s what I want to end up with:
  • Layout created by the report designer, following all conventions of the reporting tool itself
  • A clean viewmodel, so we can prepare the data we want, using LINQ etc instead of some obscure reporting binding language.
  • All databinding strongly typed (e.g. in the’ code behind’ of the report document)
I was very happy when I found the blogpost ‘Strongly-typed Telerik reports
by Jimmy Bogard. I quote: “Magic strings are maintainability grenades, and I like to squash them wherever they show up.” This post really set me on the right track. But Jimmy’s solution is for Telerik, and since we used DevExpress, I had to move some things around. The solution presetend here also uses the ‘UINameHelper‘ class (just google “UINameHelper”). Our custom class where the magic will happen is DevExpressBindingExtensions:
using System;
using System.Linq.Expressions;
using DevExpress.XtraReports.UI;

namespace Reporting
{
    public enum SimpleFormats
    {
        None,
        Date,
        Currency
    }

    public static class DevExpressBindingExtensions
    {
        public static void BindTo<TModel>(this XRLabel label, Expression<Func<TModel, object>> expression, SimpleFormats format = SimpleFormats.None)
        {
            label.DataBindings.Add(new XRBinding("Text", null, UINameHelper.BuildNameFrom(expression), GetFormatString(format)));
        }

        public static void BindVisibilityTo<TModel>(this XRControl control, Expression<Func<TModel, object>> expression)
        {
            control.DataBindings.Add(new XRBinding("Visible", null, UINameHelper.BuildNameFrom(expression)));
        }

        private static string GetFormatString(SimpleFormats format)
        {
            if (format == SimpleFormats.None) return "";
            if (format == SimpleFormats.Date) return "{0:dd/MM/yyyy}";
            if (format == SimpleFormats.Currency) return "{0:€ 0.00}";
            return "";
        }
    }
}
 Then, in a document, we can bind a label like this:
lblCompanyName.BindTo<InvoiceDocumentModel>(m => m.Invoice.Company.DisplayName);
Or we can use a simple format:
lblAmountTotal.BindTo<InvoiceDocumentModel>(m => m.Invoice.AmountTotal, SimpleFormats.Currency);

Finally, we need to address one more issue. As far as I could tell, XtraReports only allows us to bind to collections, and we have a single model instead. Let’s put the fix for that in a single place, the line where we render the actual report:

        internal byte[] RenderTemplate(XtraReport template, TModel model)
        {
            using (var memoryStream = new MemoryStream())
            {
                template.DataSource = new[] { model };        // WHY: XtraReports can only bind to collections
                template.CreateDocument();
                template.ExportToPdf(memoryStream);

                return memoryStream.ToArray();
            }
        }
This feels a lot more natural to me than the standard databinding features. What do you think? Useful or not? Any feedback is welcome!