Skip to content

Visual Studio solution-structure for Docker-project with .editorconfig, Directory.Build.props and Directory.Build.targets

December 21, 2023

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
  • Tests
    • Integration-tests
      • Integration-tests.csproj
    • Unit-tests
      • Unit-tests.csproj
    • .editorconfig
    • Directory.Build.props
    • Directory.Build.targets
  • .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.

  1. Create a new project
  2. Choose “Blank Solution”
  3. Click Next
  4. Name your solution (Solution name) – Eg. “Solution-name”
  5. Set Location – Eg. C:\Data\Projects
  6. 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:

  1. Right-click your “.root” folder -> Add -> New Item…
  2. 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

  1. Right-click the “Source”-folder -> Add -> New Project…
  2. Choose “ASP.NET Core Web App (Razor Pages)” – Just as an example.
  3. Click Next
  4. Name your project (Project name) – Eg. “Application”.
  5. Add “\Source” at the end of the Location – so eg. C:\Data\Projects\Solution-name becomes C:\Data\Projects\Solution-name\Source.
  6. Click Next
  7. Enable Docker and Docker OS: Linux
  8. Click Create

The steps above should end up with a “.dockerignore” file in the solution root.

2.4.1 Add .dockerignore to .root

  1. Right-click your “.root” folder -> Add -> Existing Item…
  2. 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.

  1. Copy “.editorconfig”, “Directory.Build.props” and “Directory.Build.targets” from your solution root to the Tests-folder in the file explorer.
  2. Back in Visual Studio, right-click your “Tests” folder -> Add -> Existing Item…
  3. 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

  1. Right-click the “Tests”-folder -> Add -> New Project…
  2. Choose “xUnit Test Project” – or another type of test-project.
  3. Click Next
  4. Name your project (Project name) “Integration-tests”.
  5. Add “\Tests” at the end of the Location – so eg. C:\Data\Projects\Your-Solution becomes C:\Data\Projects\Your-Solution\Tests.
  6. Click Next
  7. Choose framework
  8. 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

  1. Right-click the “Tests”-folder -> Add -> New Project…
  2. Choose “xUnit Test Project” – or another type of test-project.
  3. Click Next
  4. Name your project (Project name) “Unit-tests”.
  5. Add “\Tests” at the end of the Location – so eg. C:\Data\Projects\Your-Solution becomes C:\Data\Projects\Your-Solution\Tests.
  6. Click Next
  7. Choose framework
  8. 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

Links

Leave a Comment

Leave a comment