Microsofts .NET Core framework has rightfully earned its spot among cross-platform frameworks. We like to use it for example as a RESTful backend for our react frontends. If you are not burying your .NET Core application in a docker container without the need to configure/customize it you may feel agitated by its default deployment layout: All the dependencies live next to some JSON configuration files in one directory.
While this is ok if you do not need to look in there for a configuration file and change something you may like to clean it up and put the files into different folders. This can be achieved by customizing your MS build but it is all but straightforward!
Our goal
- Put all of our dependencies into a lib directory
- Put all of our configuration files int a configuration directory
- Remove unneeded files
The above should not require any interaction but be part of the regular build process.
The journey
We need to customize the MSBuild system to achieve our goal because the deps.json
file must be rewritten to change the location of our dependencies. This is the hardest part! First we add the RoslynCodeTaskFactory as a package reference to our MSbuild in the csproj of our project. That we we can implement tasks using C#. We define two tasks that will help us in rewriting the deps.json:
<Project ToolsVersion="15.8" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask TaskName="RegexReplaceFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(RoslynCodeTaskFactory)" Condition=" '$(RoslynCodeTaskFactory)' != '' "> <ParameterGroup> <InputFile ParameterType="System.String" Required="true" /> <OutputFile ParameterType="System.String" Required="true" /> <MatchExpression ParameterType="System.String" Required="true" /> <ReplacementText ParameterType="System.String" Required="true" /> </ParameterGroup> <Task> <Using Namespace="System" /> <Using Namespace="System.IO" /> <Using Namespace="System.Text.RegularExpressions" /> <Code Type="Fragment" Language="cs"> <![CDATA[ File.WriteAllText( OutputFile, Regex.Replace(File.ReadAllText(InputFile), MatchExpression, ReplacementText) ); ]]> </Code> </Task> </UsingTask> <UsingTask TaskName="RegexTrimFileText" TaskFactory="CodeTaskFactory" AssemblyFile="$(RoslynCodeTaskFactory)" Condition=" '$(RoslynCodeTaskFactory)' != '' "> <ParameterGroup> <InputFile ParameterType="System.String" Required="true" /> <OutputFile ParameterType="System.String" Required="true" /> <MatchExpression ParameterType="System.String" Required="true" /> </ParameterGroup> <Task> <Using Namespace="System" /> <Using Namespace="System.IO" /> <Using Namespace="System.Text.RegularExpressions" /> <Code Type="Fragment" Language="cs"> <![CDATA[ File.WriteAllText( OutputFile, Regex.Replace(File.ReadAllText(InputFile), MatchExpression, "") ); ]]> </Code> </Task> </UsingTask> </Project>
We put the tasks in a file called RegexReplace.targets
file in the Build directory and import it in our csproj using <Import Project="Build/RegexReplace.targets" />
.
Now we can just add a new target that is executed after the publish target to our main project csproj to move the assemblies around, rewrite the deps.json and remove unwanted files:
<Target Name="PostPublishActions" AfterTargets="AfterPublish"> <ItemGroup> <Libraries Include="$(PublishUrl)\*.dll" Exclude="$(PublishUrl)\MyProject.dll" /> </ItemGroup> <ItemGroup> <Unwanted Include="$(PublishUrl)\MyProject.pdb;$(PublishUrl)\.filenesting.json" /> </ItemGroup> <Move SourceFiles="@(Libraries)" DestinationFolder="$(PublishUrl)/lib" /> <Copy SourceFiles="Build\MyProject.runtimeconfig.json;Build\web.config" DestinationFiles="$(PublishUrl)\MyProject.runtimeconfig.json;$(PublishUrl)\web.config" /> <Delete Files="@(Libraries)" /> <Delete Files="@(Unwanted)" /> <RemoveDir Directories="$(PublishUrl)\Build" /> <RegexTrimFileText InputFile="$(PublishUrl)\MyProject.deps.json" OutputFile="$(PublishUrl)\MyProject.deps.json" MatchExpression="(?<=").*[/|\\](?=.*\.dll|.*\.exe)" /> <RegexReplaceFileText InputFile="$(PublishUrl)\MyProject.deps.json" OutputFile="$(PublishUrl)\MyProject.deps.json" MatchExpression=""path": ".*"" ReplacementText=""path": "."" /> </Target>
The result
All this work should result in a working application with a root directory layout like in the image. As far as we know the remaining files like the
web.config
, the main project assembly and the two json files cannot easily relocated. The resulting layout is nevertheless quite clean and makes it easy for administrators to find the configuration files they need to customize.
Of course one can argue if the result is worth the hassle but if your customers’ administrators and operations value it you should do it.