Visual Studio solution-structure for Docker-project with .editorconfig, Directory.Build.props and Directory.Build.targets
This is a reminder of howto structure Visual Studio solutions containing an application/project to deploy on a container platform. That is, we have a pipeline building the docker image and deploying it to a container platform. At the same time we want to make use of the following files in the solution-root:
- .editorconfig (with code analysis settings)
- Directory.Build.props
- Directory.Build.targets
I am using Visual Studio Professional 2022 (17.8.3) when writing this post.
1 Solution-structure
Examplified with a “ASP.NET Core Web App (Razor Pages)”-project. We want this solution-structure:
- Source
- Application
- Pages
- …
- Properties
- launchSettings.json
- wwwroot
- …
- Application.csproj
- appsettings.Development.json
- appsettings.json
- Dockerfile
- Program.cs
- Pages
- Application
- Tests
- Integration-tests
- …
- Integration-tests.csproj
- Unit-tests
- …
- Unit-tests.csproj
- .editorconfig
- Directory.Build.props
- Directory.Build.targets
- Integration-tests
- .dockerignore
- .editorconfig
- .gitignore
- Directory.Build.props
- Directory.Build.targets
- NuGet.config
- ReadMe.md
- Solution-name.sln
2 Howto
2.1 Solution
Create an empty Visual Studio solution.
- Create a new project
- Choose “Blank Solution”
- Click Next
- Name your solution (Solution name) – Eg. “Solution-name”
- Set Location – Eg. C:\Data\Projects
- Click Create
2.2 .root (solution-folder)
Create a “New Solution Folder” in your solution and name it “.root”. I would like to name it “.” but Visual Studio does not allow that. This is a solution-folder to make all files from your solution-root available inside Visual Studio when you work with your solution.
You create all the following files by:
- Right-click your “.root” folder -> Add -> New Item…
- Choose “Text File” and name it “file-name” (eg. .editorconfig)
2.2.1 .editorconfig
Add the following content to it:
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = crlf
indent_size = tab
indent_style = tab
insert_final_newline = false
tab_width = 4
trim_trailing_whitespace = true
[*.cshtml]
charset = utf-8-bom
# Tabs are not supported in YAML: https://yaml.org/faq.html
[*.{yaml,yml}]
indent_size = 2
indent_style = space
[*.cs]
# Set all, https://docs.microsoft.com/en-us/visualstudio/code-quality/use-roslyn-analyzers?view=vs-2019#set-rule-severity-of-multiple-analyzer-rules-at-once-in-an-editorconfig-file
dotnet_analyzer_diagnostic.severity = error
# Suppress (suppress any rule you need to suppress)
dotnet_diagnostic.CA1848.severity = none # as an example
2.2.2 .gitignore
Add the following content to it:
2.2.3 Directory.Build.props
Add the following content to it:
<Project>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<!-- Below is a workaround for https://github.com/dotnet/roslyn/issues/41640 -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<!-- Below is for the same workaround as above. -->
<NoWarn>$(NoWarn);CS1591</NoWarn>
<Nullable>enable</Nullable>
<TargetFramework>net8.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
And any other global settings you want.
2.2.4 Directory.Build.targets
Add the following content to it:
<Project />
2.2.5 NuGet.config
Add the following content to it:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<bindingRedirects>
<add key="skip" value="false" />
</bindingRedirects>
<disabledPackageSources>
<clear />
</disabledPackageSources>
<packageRestore>
<add key="automatic" value="true" />
<add key="enabled" value="true" />
</packageRestore>
<packageSources>
<clear />
<add key="nuget.org" protocolVersion="3" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>
2.2.6 ReadMe.md
Add the following content to it:
# Solution-name
2.3 Source (solution-folder)
Create a “New Solution Folder” in your solution and name it “Source”.
2.4 Add project
- Right-click the “Source”-folder -> Add -> New Project…
- Choose “ASP.NET Core Web App (Razor Pages)” – Just as an example.
- Click Next
- Name your project (Project name) – Eg. “Application”.
- Add “\Source” at the end of the Location – so eg. C:\Data\Projects\Solution-name becomes C:\Data\Projects\Solution-name\Source.
- Click Next
- Enable Docker and Docker OS: Linux
- Click Create
The steps above should end up with a “.dockerignore” file in the solution root.
2.4.1 Add .dockerignore to .root
- Right-click your “.root” folder -> Add -> Existing Item…
- Pick the “.dockerignore” file
2.4.2 Edit the .dockerignore
Add the following at the end:
# Custom
Source/Application/appsettings.json
Source/Application/appsettings.*.json
This assumes you are using configuration-maps (Kubernetes/OpenShift: ConfigMaps) in your container platform.
2.4.3 Edit the Dockerfile
Change from this (the created one should look like this):
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Directory.Build.props", "."]
COPY ["Directory.Build.targets", "."]
COPY ["NuGet.config", "."]
COPY ["Source/Application/Application.csproj", "Source/Application/"]
RUN dotnet restore "./Source/Application/./Application.csproj"
COPY . .
WORKDIR "/src/Source/Application"
RUN dotnet build "./Application.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Application.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Application.dll"]
to this:
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY [".editorconfig", "."]
COPY ["Directory.Build.props", "."]
COPY ["Directory.Build.targets", "."]
COPY ["NuGet.config", "."]
COPY ["Source/Application/Application.csproj", "Application/"]
RUN dotnet restore "./Application/Application.csproj"
COPY ["Source/", "."]
WORKDIR "/src/Application"
RUN dotnet build "./Application.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Application.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Application.dll"]
2.4.4 Edit launchSettings.json
Change from this (only the Docker section):
{
...
"profiles": {
...
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
...
}
...
}
to this:
{
...
"profiles": {
...
"Docker": {
"commandName": "Docker",
"environmentVariables": {
"ASPNETCORE_URLS": "https://+:5000"
},
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": false,
"sslPort": 5000,
"useSSL": true
}
...
}
...
}
2.5 Tests (solution-folder)
Create a “New Solution Folder” in your solution and name it “Tests”.
Also create a Tests-folder under your solution root in the file explorer, eg. C:\Data\Projects\Solution-name\Tests.
I can’t add files to the Tests-folder inside Visual Studio. If I do, they end up “physically” under the solution root. I have to create it “physically” in the file explorer.
You create all the following files by copying them from the solution root to the Tests-folder in your file explorer and then replacing the content.
- Copy “.editorconfig”, “Directory.Build.props” and “Directory.Build.targets” from your solution root to the Tests-folder in the file explorer.
- Back in Visual Studio, right-click your “Tests” folder -> Add -> Existing Item…
- Pick the “Tests/.editorconfig”, “Tests/Directory.Build.props” and “Tests/Directory.Build.targets” files
2.5.1 .editorconfig
Replace the content with this:
# Suppress (suppress any rule you need to suppress)
[*.cs]
dotnet_diagnostic.CA1707.severity = none # as an example
dotnet_diagnostic.CA1861.severity = none # as an example
2.5.2 Directory.Build.props
Replace the content with this:
<Project>
<Import Project="../Directory.Build.props" />
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>
2.5.3 Directory.Build.targets
Replace the content with this:
<Project>
<Import Project="../Directory.Build.targets" />
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../Source/Application/Application.csproj" />
</ItemGroup>
</Project>
2.6 Add Integration-tests project
- Right-click the “Tests”-folder -> Add -> New Project…
- Choose “xUnit Test Project” – or another type of test-project.
- Click Next
- Name your project (Project name) “Integration-tests”.
- Add “\Tests” at the end of the Location – so eg. C:\Data\Projects\Your-Solution becomes C:\Data\Projects\Your-Solution\Tests.
- Click Next
- Choose framework
- Click Create
2.6.1 Edit the project-file
Change the content to this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>IntegrationTests</RootNamespace>
</PropertyGroup>
</Project>
2.6.2 Remove or edit the class-file
Either remove the class-file created or change the namespace in it to “IntegrationTests”.
2.7 Add Unit-tests project
- Right-click the “Tests”-folder -> Add -> New Project…
- Choose “xUnit Test Project” – or another type of test-project.
- Click Next
- Name your project (Project name) “Unit-tests”.
- Add “\Tests” at the end of the Location – so eg. C:\Data\Projects\Your-Solution becomes C:\Data\Projects\Your-Solution\Tests.
- Click Next
- Choose framework
- Click Create
2.7.1 Edit the project-file
Change the content to this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>UnitTests</RootNamespace>
</PropertyGroup>
</Project>
2.7.2 Remove or edit the class-file
Either remove the class-file created or change the namespace in it to “UnitTests”.
3 Notes
When you build you will probably get build-errors because of the code-analysis settings we have set. You can fix the errors in any of the following ways:
- Fix the errors
- Suppress the errors you dont fix in .editorconfig, eg: dotnet_diagnostic.CA1848.severity = none
- Temporary comment out “dotnet_analyzer_diagnostic.severity = error” in .editorconfig: #dotnet_analyzer_diagnostic.severity = error