AFProj File

AFProj files are regular MSBuild project files with the .afproj extension. Developers can follow the MSBuild Project Schema and use any task available through MSBuild, AppFactory, the MSBuild Extensions Pack or any other task library of their choosing. AFProj files are executed with MSBuild.exe just like 'regular' MSBuild files and can potentially be secheduled for execution on a TFS server.

The Custom Tasks page on this wiki documents the tasks AppFactory uses that are not part of the standard MSBuild package. This page will document each step in the Sample.afproj file that ships with AppFactory and explain how it works with the front-end.

Differences Between MSBuild Projects and .AFProj Files

There are currently only two differences between 'regular' MSBuild files and AFProj files:
  1. .afproj files can be (and probably should be) associated with the UI front-end (AppFactory.exe)
  2. .afproj files have a section of properties that the UI front-end knows how to read and write.

It's the 2nd point that makes AFProj files unique. Because the front-end knows how to write values into AFProj files it can be very specific about passing parameters. It can also save its own settings to be loaded back into memory the next time the project is opened.

This is how settings like the app selection method and the custom "where" clause are persisted between runs. And since these settings are stored as part of the AFProj file, they can be different between projects. For example, the last used "where" clause for a suite of football applications might be AppName=Broncos and the last used "where" clause for a suite of music applications might be AlbumYear > 2011.

These "UI settings" are stored in a special property group at the top of the AFProj file with the label "UISettings". This property group is the only part of the file that is touched by the front-end when the project is loaded and executed. If this group is not found it will be created the first time the project is executed by the front-end. Just like all MSBuild properties, these properties can be overridden on the command propmpt with the /property:name=value syntax.

It is worth noting that named (or labeled) property groups were not supported by versions of MSBuild earlier than 4.0. MSBuild 4.0 ships with .NET 4.0.

Anatomy of the Sample.afproj File


The sample .afproj file starts like most MSBuild files with the opening <xml ...> tag followed by the <Project ...> tag. Like most MSBuild projects the default target is "All", which means if you run MSBuild.exe Sample.afproj on the command line, MSBuild will look for a <Target ...> node with the Name "All" and begin executing there. The "All" target is defined later in the file and it depends on other targets so let's talk about those first.

Using Tasks

Continuing from the top of the file, the next thing we see is a bunch of <UsingTask ... entries. These entries import the Custom Tasks that are used by this project. Notice that each path points to Tasks\DLLName.This means that there should be a folder in the same directory as the .afproj file called Tasks and inside that folder we should find the DLLs. All of the paths in Sample.afproj are relative to the project file itself, but absolute paths could be used instead if there was a need to share files between projects.

Property Groups

In the next section we see two <PropertyGroup nodes. Properties are MSBuilds way of declaring variables. Properties have a default value and that value can be overridden on the command line with the /property:name=value syntax. Properties can also be conditionally assigned values based on some other variable in the buid.

The first <PropertyGroup has a lable of "UISettings" and the properties in this group are loaded into the Front-End and written back out on each build. (See the section Differences Between MSBuild Projects and .AFProj Files above.) Properties in this group define things like the method used to select the list of applications from the database, the WHERE clause to execute and the verbosity of the output in the command window when run from the front-end.

The second <PropertyGroup is labled "ProjectSettings" and contains the main configuration settings for this project. For example it defines the OleDB connection string needed to connect to the database, the name of the table where Application Data is stored, the path to the Template project and the path to the Assets folder. At the bottom of this group is an important property called <AppQuery. AppQuery is the final SELECT statement that will be used to fetch the application rows from the database and its value is built from several other variables, some of which are passed from the front-end. As with all variables, even AppQuery can be overridden on the command line.

Loading Data

The next node we see is a <Target with the name "LoadData". LoadData is listed as a dependency of the "All" target, which means it automatically gets called at the beginning of the build.

LoadData uses a specialized task called DbRowsToItems to load application rows from the database into MSBuild Items. These are used later in the build. For more information about this task see the Custom Tasks page.

The 'All' Target

The next node is a <Target named "All" and it's where the most important work gets done. Technically the "All" node is the entry point of the build, but since properties are always evaluated first and since the "All" target depends on the "LoadData" target, everything that came before this node has already been executed by the time it's reached.

In the opening tag <Target Name="All" we see a special attribute called Outputs. It's defined like this:


The % symbol in %(Application.AppName) means something different at the opening of the tag than it means when it's used everywhere inside the tag. When % is used as part of the <Target tag it means the target will be executed once for every item in the Application collection. But inside the target tag the % refers to the current item in the collection. This is called Batching.

"LoadData" loaded application records from the database and converted them to a collection of MSBuild items, so the "All" target will get executed once for each application that was loaded into memory. Inside of the "All" target, anytime we see something like %(Application.AppName) it will get replaced with the actual value from the AppName column in the database (for the current application).

Dealing with "No Records Found"

There will be times when the SELECT statement (AppQuery) doesn't return any apps. This could happen, for example, when the "Not built since Date" option is selected and all of the applications are up to date. One of the interesting things about Batching is that the target always runs, even if the collection is empty. That means the "All" target will still run even if no applications were loaded from the database.

When that happens the %(Application) object is empty. There's no point in continuing the build so we error out with the statement "No applications matched the search criteria."

Cleaning the Workspace

The Workspace folder is the folder where everything gets copied to before it's built. This folder is configurable using the property $(WorkingPath) and its default value is simply "Working".

Since the "All" target is run repeatedly for each application, we want to make sure that this folder starts fresh on every pass. Though it's possible to do a recursive delete in MSBuild, it's usually just faster to delete the folder and recreate it. That's what the next step does, checking first to make sure the folder exists before trying to remove it.

Copying the Template

The Template folder contains the source project and all of the files that are used as the "starting point" for each build. This folder is configurable using the property $(TemplatePath) and its default value is simply "Template".

The next step in the sample project copies all of the files from the Template folder to the Working folder recursively.

Application-Specific Assets

Assets refer to the artwork, images and even audio or video that may be part of an application. Many images (like button icons) will stay the same from application to application. But some images (like logos and icons) will change.

Assets that stay the same can simply be included as part of the template in the Template folder. Assets that need to change can be placed under the Assets folder in a sub-folder that matches the application name.

The Assets folder is configurable using the $(AssetsPath) property and its default value is "Assets". The Sample project uses %(Application.AppName) as the sub-folder name so each application can have its own assets in a folder that matches this column in the database.

Assets are only copied if the sub-folder is found and when they're found they're copied recursively. Application-specific assets overwrite any files in the Working folder so they always take precidence over files in the Template. This also allows for some files to be overwritten in some applications but not in others.

Updating AssemblyInfo.cs

AssemblyInfo.cs is a special file in all .NET projects that contains information like company name, version number, product GUID, etc. AppFactory uses a custom task to make sure AssemblyInfo.cs matches the application record in the database.

For more information see the Custom Tasks page.

Detokenizing (Search and Replace)

Detokenizing is like doing a "search and replace" across the files in the working folder. The things that get replaced are called "tokens" and what they get replaced with are "values".

AppFactory uses a custom task from the MSBuld Extension Pack to do this work. That task is called Detokenise and it needs to know three things:
  1. Which files to search in
  2. Which tokens to replace
  3. The values to replace them with

Sample.afproj searches in WMAppManifest.xml and all .xaml files. WMAppManifest controls the product GUID and application title while .xaml files control the screens, resources (colors, fonts, etc) and text of the application. Additional extensions can be added here, but keep in mind that this step takes time. Including an extension like .cs may add several seconds to the build time for each application. That time adds up quickly when building hundreds of applications.

In Sample.afproj the tokens and replacement values are handled by a custom task. That task is called ItemToReplaceParams which looks at a MSBuild Item and creates replacement parameters for every property on the item. Since the Sample.afproj file passes in the current Application object, every property on the Application (meaning essentially every column in the Application table) is available as a replacement value. This is how the token $(Application.Title) in WMAppManifest.xml can get replaced with the matching column in the Application table.

For more information about the Detokenize and ItemToReplaceParams tasks, see the Custom Tasks page.

Building the App

The next step is to build the app, which MSBuild already knows how to do. MSBuild needs to know the project file, which is supplied using the combination of $(WorkingPath)\$(TemplateProject).

$(TemplateProject) is configurable and the default is "Template.csproj". This property should be changed to match the correct file and using a solution (.sln) instead of a project (.csproj) is also acceptable.

Because AppFactory can build both Debug and Release version of each app, and because AppFactory needs to know the name of the compiled app and where to find it, those properties are all passed to MSBuild.

Copying to Output Folder

Because the name of the compiled app and the folder it should be built in are known, it's easy enough to copy the resulting file to the final Output folder.

This Output folder is defined by the property $(OutputPath) and its default value is "Output". When AppFactory completes a build, this folder should have a unique .xap file for every application that was built.

Updating Last Built

The final step in the project uses a custom task to update the LastBuilt column in the database for the current application. This makes it possible to run AppFactory later and use the "Not built since Date" feature.

The custom task is called OleDbExecute and you can read about it on the Custom Tasks page.

Last edited Feb 28, 2012 at 9:03 PM by jaredbienz, version 20


No comments yet.