New in PostSharp 3.1: Solution-Level Aspects and PostSharp Configuration Files

by Gael Fraiteur on 12 Nov 2013

One of the use cases we wanted to optimize is to add logging to a whole solution as easily as possible. There are a few policies that you may want to add at solution level instead of at project level. Deadlock detection, which needs to be applied to all projects to be efficient, is another one.

In previous versions of PostSharp, you had to add these policies to each project individually. PostSharp 3.1 now makes it easier just to add them to the solution. However, this presented many challenges.

Improvements in PostSharp Tools

The first challenge was that PostSharp essentially needs to be installed to each project separately, because its file PostSharp.targets has to be included in every csproj or vbproj file. So, we still need to install the PostSharp NuGet package to all affected projects. This could already be done easily using the solution-level package management window of NuGet, but now the solution-level PostSharp wizard makes it even easier.

Remember however that you still need to install PostSharp to projects that you will later add to your solution.

Solution-Level Configuration File

If we want to add aspects to several projects from a single line, we also needed some solution-level configuration file for PostSharp. We already had a project-level file with extension psproj. We now have a solution-level file with extension pssln. The name of this file must be the same as the solution name, so if your solution is MySolution.sln, PostSharp is going to store its solution-level configuration into MySolution.pssln. Both psproj and pssln files share exactly the same syntax. Thanks to this new file, we now have somewhere to share our solution-level aspects and other shared information such as logging profiles.

For instance, adding logging to the solution would create the following project-level file:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="https://schemas.postsharp.org/1.0/configuration" 
         xmlns:d="clr-namespace:PostSharp.Patterns.Diagnostics;assembly:PostSharp.Patterns.Diagnostics" 
         xmlns:p="https://schemas.postsharp.org/1.0/configuration">
  <Property Name="LoggingBackend" Value="Console" />
  <Property Name="LoggingEnabled" Value="{has-plugin('PostSharp.Patterns.Diagnostics')}" Deferred="true" />
  <Multicast>
    <When Condition="{$LoggingEnabled}">
      <d:Log AttributeTargetTypeAttributes="Public" AttributeTargetMemberAttributes="Public" />
    </When>
  </Multicast>
  <d:LoggingProfiles p:Condition="{$LoggingEnabled}">
    <d:LoggingProfile Name="Default" 
        OnEntryOptions="IncludeParameterName | IncludeParameterValue | IncludeThisArgument" 
        OnSuccessOptions="IncludeParameterName | IncludeParameterValue | IncludeReturnValue | IncludeThisArgument" 
        OnExceptionOptions="IncludeParameterName | IncludeParameterValue | IncludeThisArgument" />
  </d:LoggingProfiles>
</Project>

Here is the project-level file you will get by default:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="https://schemas.postsharp.org/1.0/configuration">
  <Using 
File="..\packages\PostSharp.Patterns.Diagnostics.3.1.18-beta\tools\PostSharp.Patterns.Diagnostics.Weaver.dll" /> </Project>

I’ll give more details below about expressions and conditional constructs.

It’s worth noting that PostSharp has had a configuration file since version 1.0. Very soon, I understood it was better to design a system that did not require configuration, so I implemented mechanisms that rendered configuration files unnecessary for most users. We re-introduced the configuration files when we implemented NuGet packaging, because we needed a way to tell PostSharp where the plug-ins (packaged as different NuGet packages) were installed.

It’s also important to understand that this features introduces some kind of “oddity” in the build process, because it usually does not matter if you build a project A from solution B or C, or from no solution at all. Usually, the build process of a project does not depend on the solution from which the build process was started. With this feature however, it does. You should be aware of this issue, and not use solution-level configuration if you plan to build from several solutions.

UPDATE: the next paragraphs of this section have been updated to reflect improvements in build 3.1.21:

The reason why we chose the solution-level configuration file is that it is a simpler model and that it is significantly simpler to build a UI for a simpler model. But once you’re past the Hello World stage, you’ll find more convenient to interact with PostSharp as it was a compiler. Because it, really, is.

There are two alternative ways to share configuration files among several projects. The first way is to add a file named postsharp.config in the directory containing the C#/VB project file or in any parent directory, up to the root. These files will be loaded in order of increasing directory depth, before the psssln file and before the psproj file, so in the following order for instance:

  1. c:\src\MySolution\postsharp.config
  2. c:\src\MySolution\BackEnd\postsharp.config
  3. c:\src\MySolution\BackEnd\MyProject\postsharp.config
  4. c:\src\MySolution\MySolution.pssln
  5. c:\src\MySolution\BackEnd\MyProject\MyProject.psproj

The second way is to add a Using element to your project file, just as in the preceding XML snippet, but pointing to your configuration file instead of to a dll.

Note that these two alternative ways are not supported by the UI, just by the compiler.

XPath expressions

Another challenge we had to solve is that a solution may contain projects that target different platforms, and that not all aspects are available for all platforms. Specifically, our logging aspect is currently only available for .NET 4.0, so for instance you can’t add it to a Windows Phone project. Thus, we needed a way to make solution-level aspects conditional. This is why we added two features to the configuration file: XPath, and conditional constructs.

For the anecdote, I’ve always planned to add XPath support to the configuration file format. However, I only implemented expansion of properties, pretty much like MSBuild 2.0 did. But at the difference of MSBuild, property references were forward compatible with the XPath syntax. For instance {$MyProperty} results in the evaluation of the MyProperty – it previously just was done by property expansion, but now it can be considered as an XPath expression.

The following features of XPath 1.0 are available:

  • Any XPath 1.0 function of the Microsoft’s implementation.
  • XPath variables, for instance $MyProperty, evaluate to PostSharp properties (not to MSBuild properties).
  • Custom function has-plugin(name) evaluates to true if a plug-in is loaded, false otherwise.
  • Custom function environment(variable) returns the value of an environment variable.

In theory, the design would allow for plug-ins to define custom functions, but we did not implement this feature since we did not have any use case.

Conditional constructs

    Some constructs now have a Condition attribute, which is typically set to a boolean XPath expression:
  • The following first-level elements: Property, Using, Multicast, LoggingProfiles
  • Any child element of Multicast or LoggingProfiles

Additionally, elements Multicast and LoggingProfiles accept a child element When, as illustrated in the XML snippet above. All children of the When element are made conditional.

Properties with deferred evaluation

You may have noted the Deferred attribute on the definition of the LoggingEnabled property in the first code snippet. This means that the value of the expression will be evaluated dynamically whenever the property value is requested. By default, a property value is evaluated immediately at the time the property is defined.

In this case, it is useful to have deferred evaluation of the LoggingEnabled property because its value depends on the has-plugin method, and that plug-ins are typically loaded after the property is evaluated: indeed, the property is defined at solution level, and plug-ins are loaded at project level.

Summary

The PostSharp configuration system got a major upgrade in PostSharp 3.1 with XPath expressions and conditional constructs – two necessary features if we wanted to enable for solution-level aspects and solution-level shared configuration.

We understand the documentation of the configuration system is currently weak and we’ll be working on that before the RTM. However, it’s already become much easier to add aspects that really cross-cut all projects.

Happy PostSharping!

-gael