In the previous article , we inspected the pre-compilation sections of the MSBuild generated from a Minimal API template. In this article, we’ll examine the post-compilation sections of the same MSBuild.

Inspecting the post-compilation tasks

_GenerateScopedCssFiles

This target attaches scopes to the selectors in all the scoped CSS files generated from the previous targets. It first creates a directory for the CSS file then adds the scopes using the RewriteCss task.

The description of the target in the MSBuild XML file shows its conditions and tasks:

<Target Name="_GenerateScopedCssFiles" Inputs="@(_ScopedCss)" Outputs="@(_ScopedCssOutputs)" DependsOnTargets="ResolveScopedCssOutputs">
    <MakeDir Directories="$(_ScopedCssIntermediatePath)" />
    <RewriteCss FilesToTransform="@(_ScopedCss)" />
    <ItemGroup>
      <FileWrites Include="%(_ScopedCss.OutputFile)" />
    </ItemGroup>
</Target>

We can also see this task as part of the Scoped CSS pipeline .

<!-- omitted for brevity -->
<!-- Gathers input source files for Razor component generation. -->
<Target Name="ResolveScopedCssInputs"><!-- omitted for brevity --></Target>
<!-- This target just generates a Scope identifier for the items that we deemed were scoped css files -->
<Target Name="ComputeCssScope" DependsOnTargets="ResolveScopedCssInputs"><!-- omitted for brevity --></Target>
<!-- Sets the output path for the processed scoped css files. They will all have a '.rz.scp.css' extension to flag them as processed scoped css files. -->
<Target Name="ResolveScopedCssOutputs" DependsOnTargets="$(ResolveScopedCssOutputsDependsOn)"><!-- omitted for brevity --></Target>
<!-- Transforms the original scoped CSS files into their scoped versions on their designated output paths -->
<Target Name="_GenerateScopedCssFiles" Inputs="@(_ScopedCss)" Outputs="@(_ScopedCssOutputs)" DependsOnTargets="ResolveScopedCssOutputs"><!-- omitted for brevity --></Target>

This target is not executed for a template minimal api because there are no generated CSS files.

_GenerateScopedCssFiles:
       Skipping target "_GenerateScopedCssFiles" because it has no outputs.

_BuildCopyStaticWebAssetsPreserveNewest

This target copies the static assets into the matching directory in the build.

<Target Name="_BuildCopyStaticWebAssetsPreserveNewest" Inputs="@(_BuildStaticWebAssetsPreserveNewest)" Outputs="@(_BuildStaticWebAssetsPreserveNewest->'%(TargetPath)')" AfterTargets="_SplitStaticWebAssetsByCopyOptions">
    <Copy SourceFiles="@(_BuildStaticWebAssetsPreserveNewest)" DestinationFiles="@(_BuildStaticWebAssetsPreserveNewest->'%(TargetPath)')" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForPublishFilesIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForPublishFilesIfPossible)">
      <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
    </Copy>
</Target>

This target is also not executed for a template minimal api.

_BuildCopyStaticWebAssetsPreserveNewest:
       Skipping target "_BuildCopyStaticWebAssetsPreserveNewest" because it has no outputs.

_CopyFilesMarkedCopyLocal

This target copies files marked as CopyLocal and its dependencies to the output directory. The target uses the Copy command using the property ReferenceCopyLocalPaths that defines the set of items to be copied locally based on the project’s references.

<Target Name="_CopyFilesMarkedCopyLocal" Condition="'@(ReferenceCopyLocalPaths)' != ''">
    <PropertyGroup>
      <!-- By default we're not using Hard Links to copy to the output directory, and never when building in VS -->
      <CreateHardLinksForCopyLocalIfPossible Condition="'$(BuildingInsideVisualStudio)' == 'true' or '$(CreateHardLinksForCopyLocalIfPossible)' == ''">false</CreateHardLinksForCopyLocalIfPossible>
      <CreateSymbolicLinksForCopyLocalIfPossible Condition="'$(BuildingInsideVisualStudio)' == 'true' or '$(CreateSymbolicLinksForCopyLocalIfPossible)' == ''">false</CreateSymbolicLinksForCopyLocalIfPossible>
    </PropertyGroup>
    <Copy SourceFiles="@(ReferenceCopyLocalPaths)" DestinationFiles="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyLocalIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyLocalIfPossible)" Condition="'$(UseCommonOutputDirectory)' != 'true'">
      <Output TaskParameter="DestinationFiles" ItemName="FileWritesShareable" />
      <Output TaskParameter="CopiedFiles" ItemName="ReferencesCopiedInThisBuild" />
      <Output TaskParameter="WroteAtLeastOneFile" PropertyName="WroteAtLeastOneFile" />
    </Copy>
    <!-- If this project produces reference assemblies *and* copied (possibly transitive)
         references on this build, subsequent builds of projects that depend on it must
         not be considered up to date, so touch this marker file that is considered an
         input to projects that reference this one. -->
    <Touch Files="@(CopyUpToDateMarker)" AlwaysCreate="true" Condition="'@(ReferencesCopiedInThisBuild)' != '' and '$(WroteAtLeastOneFile)' == 'true'" />
    <ItemGroup>
      <FileWrites Include="@(CopyUpToDateMarker)" />
    </ItemGroup>
</Target>

Assemblies are identified as copy local via the CopyLocal property of the class representing resolved assemblies.

The template minimal API project has two dependencies pre-installed, Microsoft.AspNetCore. OpenApi and Swashbuckle.AspNetCore, and their assemblies are copied from the local NuGet cache into the output directory \bin for the debug build.

_CopyFilesMarkedCopyLocal:
     Copying file from "C:\Users\you\.nuget\packages\microsoft.aspnetcore.openapi\8.0.11\lib\net8.0\Microsoft.AspNetCore.OpenApi.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\Microsoft.AspNetCore.OpenApi.dll".
     Copying file from "C:\Users\you\.nuget\packages\swashbuckle.aspnetcore.swagger\6.6.2\lib\net8.0\Swashbuckle.AspNetCore.Swagger.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\Swashbuckle.AspNetCore.Swagger.dll".
     Copying file from "C:\Users\you\.nuget\packages\microsoft.openapi\1.6.14\lib\netstandard2.0\Microsoft.OpenApi.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\Microsoft.OpenApi.dll".
     Copying file from "C:\Users\you\.nuget\packages\swashbuckle.aspnetcore.swaggerui\6.6.2\lib\net8.0\Swashbuckle.AspNetCore.SwaggerUI.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\Swashbuckle.AspNetCore.SwaggerUI.dll".
     Copying file from "C:\Users\you\.nuget\packages\swashbuckle.aspnetcore.swaggergen\6.6.2\lib\net8.0\Swashbuckle.AspNetCore.SwaggerGen.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\Swashbuckle.AspNetCore.SwaggerGen.dll".
     Creating "D:\Projects\App-Workspace\Csharp\minimalref\obj\Debug\net8.0\minimalref.csproj.Up2Date" because "AlwaysCreate" was specified.
     Touching "D:\Projects\App-Workspace\Csharp\minimalref\obj\Debug\net8.0\minimalref.csproj.Up2Date".

_CopyOutOfDateSourceItemsToOutputDirectory

Similar to the previous target, this target copies files into the output directory. In this case, the files to be copied are source files that have been marked as PreserveNewest; the version in the output directory must always be kept up to date.

<Target Name="_CopyOutOfDateSourceItemsToOutputDirectory" Condition=" '@(_SourceItemsToCopyToOutputDirectory)' != '' " Inputs="@(_SourceItemsToCopyToOutputDirectory)" Outputs="@(_SourceItemsToCopyToOutputDirectory->'$(OutDir)%(TargetPath)')">
    <Copy SourceFiles="@(_SourceItemsToCopyToOutputDirectory)" DestinationFiles="@(_SourceItemsToCopyToOutputDirectory->'$(OutDir)%(TargetPath)')" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForAdditionalFilesIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForAdditionalFilesIfPossible)">
      <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
    </Copy>
</Target>

For base projects, the main source files are the app settings files and, the native executable assembly (apphost.exe) used to run the application by executing the code in the app’s DLL. We can see these files copied into the output directory in the build log.

_CopyOutOfDateSourceItemsToOutputDirectory:
     Copying file from "D:\Projects\App-Workspace\Csharp\minimalref\appsettings.Development.json" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\appsettings.Development.json".
     Copying file from "D:\Projects\App-Workspace\Csharp\minimalref\appsettings.json" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\appsettings.json".
     Copying file from "D:\Projects\App-Workspace\Csharp\minimalref\global.json" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\global.json".
     Copying file from "D:\Projects\App-Workspace\Csharp\minimalref\obj\Debug\net8.0\apphost.exe" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\minimalref.exe".

CopyFilesToOutputDirectory

Finally, this target copies the build outputs (.dll or .exe) and other necessary files e.g . pdb to the output directory. The list of files copied are listed in the target:

<Target Name="CopyFilesToOutputDirectory" DependsOnTargets="&#xD;&#xA;            ComputeIntermediateSatelliteAssemblies;&#xD;&#xA;            _CopyFilesMarkedCopyLocal;&#xD;&#xA;            _CopySourceItemsToOutputDirectory;&#xD;&#xA;            _CopyAppConfigFile;&#xD;&#xA;            _CopyManifestFiles;&#xD;&#xA;            _CheckForCompileOutputs;&#xD;&#xA;            _SGenCheckForOutputs">
    <!-- omitted for brevity -->
    <!-- Copy the build product (.dll or .exe). -->
    <Copy SourceFiles="@(IntermediateAssembly)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" ErrorIfLinkFails="$(ErrorIfLinkFailsForCopyFilesToOutputDirectory)" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"><!-- omitted for brevity --></Copy>
    <!-- Copy the reference assembly build product (.dll or .exe). -->
    <CopyRefAssembly SourcePath="@(IntermediateRefAssembly)" DestinationPath="$(TargetRefPath)" Condition="'$(ProduceReferenceAssembly)' == 'true' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"><!-- omitted for brevity --></CopyRefAssembly>
    <!-- omitted for brevity -->
    <!-- Copy the additional modules. -->
    <Copy SourceFiles="@(AddModules)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyAdditionalFilesIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyAdditionalFilesIfPossible)" Condition="'@(AddModules)' != ''"><!-- omitted for brevity --></Copy>
    <!-- Copy the serialization assembly if it exists. -->
    <Copy SourceFiles="$(IntermediateOutputPath)$(_SGenDllName)" DestinationFiles="$(OutDir)$(_SGenDllName)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" ErrorIfLinkFails="$(ErrorIfLinkFailsForCopyFilesToOutputDirectory)" Condition="'$(_SGenDllCreated)'=='true'"><!-- omitted for brevity --></Copy>
    <!-- Copy the debug information file (.pdb), if any -->
    <Copy SourceFiles="@(_DebugSymbolsIntermediatePath)" DestinationFiles="@(_DebugSymbolsOutputPath)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" ErrorIfLinkFails="$(ErrorIfLinkFailsForCopyFilesToOutputDirectory)" Condition="'$(_DebugSymbolsProduced)'=='true' and '$(SkipCopyingSymbolsToOutputDirectory)' != 'true' and '$(CopyOutputSymbolsToOutputDirectory)'=='true'"><!-- omitted for brevity --></Copy>
    <!-- Copy the resulting XML documentation file, if any. -->
    <Copy SourceFiles="@(DocFileItem)" DestinationFiles="@(FinalDocFile)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" ErrorIfLinkFails="$(ErrorIfLinkFailsForCopyFilesToOutputDirectory)" Condition="'$(_DocumentationFileProduced)'=='true' and '$(CopyDocumentationFileToOutputDirectory)'=='true'"><!-- omitted for brevity --></Copy>
    <!-- Copy satellite assemblies. -->
    <Copy SourceFiles="@(IntermediateSatelliteAssembliesWithTargetPath)" DestinationFiles="@(IntermediateSatelliteAssembliesWithTargetPath->'$(OutDir)%(Culture)\$(TargetName).resources.dll')" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" ErrorIfLinkFails="$(ErrorIfLinkFailsForCopyFilesToOutputDirectory)" Condition="'@(IntermediateSatelliteAssembliesWithTargetPath)' != ''"><!-- omitted for brevity --></Copy>
    <!--
        Copy COM reference wrappers, isolated COM references, COM references included by
        native (manifest) references, native (manifest) reference files themselves.
        -->
    <Copy SourceFiles="@(ReferenceComWrappersToCopyLocal); @(ResolvedIsolatedComModules); @(_DeploymentLooseManifestFile); @(NativeReferenceFile)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyAdditionalFilesIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyAdditionalFilesIfPossible)" Condition="'@(ReferenceComWrappersToCopyLocal)' != '' or '@(ResolvedIsolatedComModules)' != '' or '@(_DeploymentLooseManifestFile)' != '' or '@(NativeReferenceFile)' != '' "><!-- omitted for brevity --></Copy>
    <!-- Copy the build product of WinMDExp. -->
    <Copy SourceFiles="@(WinMDExpArtifacts)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" ErrorIfLinkFails="$(ErrorIfLinkFailsForCopyFilesToOutputDirectory)" Condition="'$(SkipCopyWinMDArtifact)' != 'true' and '@(WinMDExpArtifacts)' != ''"><!-- omitted for brevity --></Copy>
</Target>

The copy tasks are reflected in the build log.

CopyFilesToOutputDirectory:
     Copying file from "D:\Projects\App-Workspace\Csharp\minimalref\obj\Debug\net8.0\minimalref.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\minimalref.dll".
     Copying reference assembly from "obj\Debug\net8.0\refint\minimalref.dll" to "D:\Projects\App-Workspace\Csharp\minimalref\obj\Debug\net8.0\ref\minimalref.dll".
     minimalref -> D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\minimalref.dll
     Copying file from "D:\Projects\App-Workspace\Csharp\minimalref\obj\Debug\net8.0\minimalref.pdb" to "D:\Projects\App-Workspace\Csharp\minimalref\bin\Debug\net8.0\minimalref.pdb".

Summary

We’ve now covered the pre-compilation and post-compilation targets for a template Minimal API project. We’ll take a look at the compilation target in the next part.

I stumbled on an excellent breakdown of a .NET app while conducting the research for this post and would like to share it with you here: