Flexible CSV exports over web-api with server side MEF. (part 1)

Introduction

Since the introduction of LightSwitch V3, the amount of plumbing options we have in LightSwitch increased drastically. Especially when using it in combination with proven technologies like web-api and the new ServerApplicationContext in LightSwitch V3.

Today, we’ll add another technology: the managed extensibility  framework. Not just for the sake of the technology, but for solving a real live problem: flexible exports.

What do I mean with flexible exports

Let’s first clearly define that an export is not the same as a report. An export simply retrieves data from the server and presents it to the user in the form of a kind of tabular structure which can be opened by the user in Excel (the most portable format for this is CSV, comma separated value).

The most simple export is the one that available out of the box in LightSwitch. This export is ok for simple usage, but in most cases the user would like to have a richer set of potential export definitions she can select from.

So, wouldn’t it be nice that instead of a simple Customer export, the user could have some more options when she clicks the export button:

selectionscreen

Obviously, we want to reuse the approach via web-api  I documented over here: reporting via web-api.

In this post I want to focus on an elegant way to define the different export definitions.

It’s all about projection strategies

In order to come up with a good approach, let’s first focus on what different between the above (potentially over simplified) export definitions.

Basically, the three definitions are just variations on the applied “projection strategy”.  A projection is a prominent “Linq” concept. When you do a select new (with is the core element in our web-api based exporting solution I referred to above) you are applying a projection.

 

An example can clarify:

    {
        public Expression<Func<Customer, CustomerCSV1>> GetProjection()
        {
            return (Customer c) => new CustomerCSV1 { FullName = c.FirstName + " " + c.LastName };
        }
    }

The above projection goes together with following POCO class.

 public class CustomerCSV1
    {
        public string FullName { get; set; }
    }

So, our customer entity is projected into a new POCO type (CustomerCSV1) which massages the data into a structure with only a FullName field (which is a concatenation of FirstName and LastName). In mathematical terms the Customer class is the “Source” and the CustomerCSV1 class is the “domain” (the destination if you want). The data are projected from source to domain.

That’s easy. Another projection can incorporate the underlying orders of the customer:

public Expression<Func<Customer, CustomerCSV3>> GetProjection()
        {
            return (Customer c) => new CustomerCSV3 { FullName= c.FirstName + " " + c.LastName, OrderCount = c.Orders.Count()};
        }

Based on following domain poco:

public class CustomerCSV3
    {
        public string FullName { get; set; }
        public int OrderCount { get; set; }
    }

It is clear that even the most flexible export solution can not avoid that the above projection strategy and the corresponding POCO have to be created in code. But our goal is that this is the only thing we need to do: new projection strategies should be resolved by the system automatically !

 That’s why we need the Managed Extensibility framework.

Sometimes people compare MEF with an IOC (inversion of control) container, but that’s not completely accurate. MEF brings in a way imports and exports (euh… not data exports as above, but code functionality of course)  together by means of attribute decoration. But, I said already too much, no MEF tutorial here. Use your browser.

Adding a new strategy to our CSV export machinery would look as follows:

  [Export(typeof(IProjection))]
  [ExportProjection( "First Name only")]

    public class CustomerFirstNameProjection : IProjection<Customer, CustomerCSV2>
    {
        public Expression<Func<Customer, CustomerCSV2>> GetProjection()
        {
            return (Customer c) => new CustomerCSV2 { FirstName = c.FirstName };
        }
    }

As you can see we decorated the CustomerFirstNameProjection with two attributes:

  • [Export(typeof(IProjection))] : tells that we want to include this strategy in our export repository. By doing so, our export selection window is automatically updated with another export template !
  • [ExportProjection(“First Name only”)]: we want to provide the export strategy a meaningful name.

 

What’s next?

The implementation of the above. Be prepared for pretty technical code, but remember it’s infrastructure code, the goals is writing less code when using it !