Tuesday, October 28, 2014

Onion application architecture and web deploy packaging

A principle of Onion architecture is the inner layers cannot have dependencies on outer layers (i.e. dependencies are inward only). This implies that dependency inversion is key to ensure this principle is not broken.

This does not play nicely with Web Deploy and packaging out of the box. The dependency inward only principle results in outer layer libraries referencing the inner layer libraries. This means when you build the inner layer libraries, the outer layer libraries won't be built (because there is no reference to them). An option to get around this issue is to perform a post build action for the outer libraries to copy the dll to a location that the inner library can resolve an instance of the concrete dependency at runtime (e.g. using MEF). If you use Web Deploy you will need another solution...

Long story short, Web Deploy won't include extra files without some customization. By extra files I mean files that the project / library is not aware of (or in other words is not referencing) - which ties in with the above problem where inner projects don't reference outer projects - when when publishing a web project, extra files / outer layer projects will not be included in the publishing process. 

You can instruct MSBuild / Web Deploy to include extra files by adding the following to your web project file:

  <PropertyGroup>

    <CopyAllFilesToSingleFolderForMsdeployDependsOn>

      ExternalDependencies;

      $(CopyAllFilesToSingleFolderForMsdeployDependsOn);

    </CopyAllFilesToSingleFolderForMsdeployDependsOn>

  </PropertyGroup>

And,

  <Target Name="ExternalDependencies">

    <ItemGroup>

      <_CustomFiles Include="..\ExternalDependencies\*" />

      <FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">

        <DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>

      </FilesForPackagingFromProject>

    </ItemGroup>

  </Target>

This blog post has a great summary on these statements. However I'll summarize the highlighted parts above:

  • CopyAllFilesToSingleFolderForMsdeployDependsOn: Pretty descriptive, and will add the ExternalDependencies target to the packaging process.
  • ExternalDependencies: Custom target added will will define where the additional file (external dependencies) are located.
  • ..\ExternalDependencies\*: Actual location of the additional files to be included in the packaging process, i.e. the outer layer libraries. So typically I would have my external / out layer libraries have their build output copied to this location (e.g. post build action). And then when a publish is kicked off, these libraries would be picked up, and copied to the bin folder (bin\%(RecursiveDir)%(Filename)%(Extension), the last highlight part) of the web app.