Include angular generated files in msbuild package

02 Oct 2018

Angular framework helps us to streamline and build complex Single Page Applications in a structured manner which can be easily maintained and scaled to a large size. The Angular CLI (Command Line Interface) provides easier commands to scaffold entire app skeleton, modules, components, directives etc., and also provides commands to build/generate output for development or productions servers.

 While building Angular application using the CLI, it optimizes the generated code like unused code elimination, minification etc.,,  and also along with that, it adds hashes to the generated filenames. Hashed filenames is the most effective way (cache busting mechanism) to make sure that your latest scripts, styles, html gets to the users on client browser request after each new application version deployments.

For ASP .NET MVC, Dynamic filenames implies that it cannot be statically included in project .csproj. So on MSBuild, these files are ignored from the generated deploy package. In-order to instruct msbuild to package these external files and folders, we would need to make few amendments in the cs project file (.csproj).

Consider a project structure as below

Asp.net MVC with an Angular 6 application

Your angular generated output will be within 'ng/dist/' folder. By default, this folder will be ignored as it is not included as part of the project.

To custom include these generated files, open your .csproj in visual studio code (or any text editor) and add following sections after the import tags

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
  <Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
    <AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
  </Target>
<!-- Add following property group -->
  <PropertyGroup>
    <CopyAllFilesToSingleFolderForPackageDependsOn>
      CollectAngularFiles;
      $(CopyAllFilesToSingleFolderForPackageDependsOn);
    </CopyAllFilesToSingleFolderForPackageDependsOn>
  </PropertyGroup>
  <PropertyGroup>
    <CopyAllFilesToSingleFolderForMsdeployDependsOn>
      CollectAngularFiles;
      $(CopyAllFilesToSingleFolderForPackageDependsOn);
    </CopyAllFilesToSingleFolderForMsdeployDependsOn>
  </PropertyGroup>

You can see that we are adding a hook to the package CopyAllFiles action/task for both normal Filesystem publish & MsDeploy package. CollectAngularFiles points to the target action that should be performed before the normal CopyAllFiles.

Then scroll down somewhere below in the .csproj file and add following Target section.

<!-- Copy angular 6 app files -->
  <Target Name="CollectAngularFiles">
    <ItemGroup>
      <GeneratedScripts Include="ng\dist\**\*" />
      <FilesForPackagingFromProject Include="%(GeneratedScripts.Identity)">
        <DestinationRelativePath>ng\dist\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
      </FilesForPackagingFromProject>
    </ItemGroup>
  </Target>

<!-- Visual Studio clean action will clean the angular generated scripts too, using following msbuild task -->
  <Target Name="CleanDist" AfterTargets="Clean">
    <ItemGroup>
      <FilesToDelete Include="ng\dist\**;" />
    </ItemGroup>
    <Delete Files="@(FilesToDelete)" />
    <RemoveDir Directories="ng\dist" />
  </Target>

In CollectAngularFiles, we are including all files within ng\dist\ folder using wildcard pattern and also we are defining the destination path (In above code we just copy the generated files to the same directory structure). Now open the project in Visual Studio, and perform a publish using either a Filesystem or msdeploy. You'll notice that all files in the 'ng\dist\' folder are added to the publish artifact as expected.

Hope this article simplifies the required setup to copy external generated files.

I''m happy to hear your comments.