Azure Service Packages with Unreferenced Assemblies

Applications often rely on assemblies that are not statically linked (i.e. referenced) from their projects. A common scenario is to have exchangeable components that implement a well-know interface but are only resolved at runtime (e.g. accordingly to some entry on web.config). This can happen when using custom log4net appenders, ASP.NET membership providers, trace listeners and, of course, any other application-specific types. Somewhere along the build process you need to copy the needed assemblies to the “bin” folder so that it can be located at runtime.

Let me introduce an example. Consider the following ultra-useful interface and the Visual Studio projects illustrated bellow.

public interface ISomeInterface
{
	int GenerateSomeNumber();
}
The projects are organized as follows:
  • ISomeInterface  is defined on the Common assembly.
  • The Web application references Common and has a page that shows some numbers obtained through an ISomeInterface.  The name of the type of the actual ISomeInterface realization is read from web.config and the type is instantiated using reflection.
  • The Impl assembly defines the type that implements ISomeInterface.
  • The CloudServicePackage is a cloud project that references the web application as a web role.
The final objective is to include the Impl assembly on the could project’s web role so that the ISomeInterface realization can be used.
I don’t want any dependencies from Web to Impl other than configuration. Thus, my first approach was to copy the Impl assembly to the Web\bin folder on a post-build event of Impl build process. And this actually worked when running the cloud project on the development fabric. However, when I published the project, the Impl assembly wasn’t included in the package.. What?!
After digging a bit into the build output and the build targets definition (on {Program Files x86}\MSBuild\Microsoft\Cloud Service\1.0\Visual Studio 10.0\Microsoft.CloudService.targets) it seems that the build behaves differently between a “build-and-run” and a publish operation (there is a PackageWebRole variable which is set to false in the first and to true in the last). In the first case, the process is simpler: there are validations of service definition and configuration, the role references are resolved and the web application binaries and content seem to be copied to the cloud project’s output folder. However, the package itself is not created. On the other hand, when publishing the service, after the previous actions, additional things are happening, namely with respect to resolving the needed files and assemblies. The build process seems to gather the final list of files accordingly to the project’s items and dependencies! This means that despite being on the output folder, the Impl assembly is not included in the package.
I don’t fully understand the reason, but it is restrictive. So we need an alternative. Since the build ensures, at some point, that the “unneeded” files are not present, the next option was to copy the Impl assembly directly into the cloud project’s output folder after that point. After further digging into the build output and Microsoft.CloudService.targets, I extracted some of the important steps on the build process (many targets/tasks were removed for readability):
Build-and-run:
Target "FindServiceDefinition"
Target "FindServiceConfiguration"
Target "ValidateRoleProjects"
Target "ValidateServiceFiles"
Target "ValidateComputeProject"
Target "ResolveWebRoleReferences"
  Target "ResolveSingleWebRoleReference"
    Task "Message"
      PackageWebRole=False
    Task "Message"
      WebRoleProject=..\Azure.UnreferencedDependencies.Web\Azure.UnreferencedDependencies.Web.csproj
    Task "Message"
      IntermediateWebOutputPathItem=obj\Debug\Azure.UnreferencedDependencies.Web\
    Task "MakeDir"
    Task "Copy"
      (list of files in the Web bin directory here!)
Target "CopyServiceDefinitionAndConfiguration"
Target "CorePackageComputeService"
  Task "Message"
    OutputDirectory is (...)bin\Debug\Azure.UnreferencedDependencies.CloudServicePackage.csx\
  Task "Message"
    PackRoles  is (...)Azure.UnreferencedDependencies.CloudServicePackage\obj\Debug\Azure.UnreferencedDependencies.Web\
  Task "CSPack"
Publish:
Target "BeforePublish"
Target "PrepareForPackaging"
Target "ValidateRoleProjects"
Target "ValidateServiceFiles"
Target "ValidateComputeProject"
Target "ResolveWebRoleReferences"
  Target "ResolveSingleWebRoleReference"
    Task "Message"
      IntermediateWebOutputPathItem=obj\Debug\Azure.UnreferencedDependencies.Web\
    Task "MakeDir"
    Task "Copy"
(full list of Web project files here!)
Target "CopyServiceDefinitionAndConfiguration"
Target "CorePackageComputeService"
  Task "Message"
    OutputDirectory is (...)bin\Debug\Azure.UnreferencedDependencies.CloudServicePackage.csx\
  Task "Message"
    PackRoles is (...)Azure.UnreferencedDependencies.CloudServicePackage\obj\Debug\Azure.UnreferencedDependencies.Web\
  Task "CSPack"
Target "CheckRoleInstanceCount"
Target "CorePublish"
  Task "Message"
    CorePublish: PackageWebRole = True
  Task "Message"
    Publishing to '(...)bin\Debug\Publish'
  Task "Message"
    Roles is (...)Azure.UnreferencedDependencies.CloudServicePackage\obj\Debug\Azure.UnreferencedDependencies.Web\
  Task "CSPack"
The process is similar: the roles are resolved, compiled and copied to obj the folder and then “packaged” into the bin folder. The main difference is in the list of files that are included (probably controlled by the PackageWebRole flag). In the first case seems that only the Web role’s bin folder is copied (in a sort of relax mode) while in the second there’s a strict list of the files to be included. Anyway, the obj folder seems to be the base for the final package creation. And it has to be ready (i.e. with all the needed files) right before the CorePackageComputeService target. So we just need to ensure that it is!
A possible solution is to define a target on the cloud project that copies the Impl assembly to the appropriate place inside the output folder. In this case, I want it to be copied to the bin folder of the Web app inside the package output folder (obj). Unload the cloud project and add the following code:
<ItemGroup>
  <LateBoundAssemblies Include="$(SolutionDir)Azure.UnreferencedDependencies.Impl\bin\$(Configuration)\Azure.UnreferencedDependencies.Impl.dll">
    <Destination>Azure.UnreferencedDependencies.Web\bin\Azure.UnreferencedDependencies.Impl.dll</Destination>
  </LateBoundAssemblies>
</ItemGroup>

<Target Name="CopyLateBoundAssemblies" BeforeTargets="CorePackageComputeService">
  <Copy SourceFiles="@(LateBoundAssemblies->'%(Identity)')"
	  DestinationFiles="@(LateBoundAssemblies->'$(ProjectDir)obj\$(Configuration)\%(Destination)')" />
  <!-- Using full IIS support; also copy to the bin on the web project so that it works on dev fabric -->
  <Copy SourceFiles="@(LateBoundAssemblies->'%(Identity)')"
          DestinationFiles="@(LateBoundAssemblies->'$(SolutionDir)%(Destination)')" />
</Target>
With this actions, when the CorePackageComputeService target runs, the Impl assembly will be in the right folder and will be packaged along. This technique also keeps these kind of actions contained on the cloud project and enables the inclusion of assemblies that are not part of the solution.
Advertisements

7 thoughts on “Azure Service Packages with Unreferenced Assemblies

    • Hi,

      The target is added on the cloud project itself. You don’t need to include it on the web app project file. When it runs before target “CorePackageComputeService” the web app was already built and copied to the package dir (as part of “ResolveSingleWebRoleReference”, I think), which means the custom target just needs to copy the binaries directly to the web app dir INSIDE the package..

  1. Luiz,

    Just one more question – what is the method you used to load the unreferenced file? The static methods in Load, LoadFrom and LoadFile in the Assembly class do not work. Any recommendations?

    Thanks.

  2. Great post – worked for me. I did run into a problem where attempting to copy a library required by spatialite (libstdc++-6.dll) it changed the name of the ++ to %2B%2B. I ended up not using the library anyway but just wanted to add this piece of information. Maybe you know how to work around that issue.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s