Skip to content

Instantly share code, notes, and snippets.

@Gameghostify
Last active July 21, 2020 15:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gameghostify/045382dd75a0888afd213fd89a34d1a6 to your computer and use it in GitHub Desktop.
Save Gameghostify/045382dd75a0888afd213fd89a34d1a6 to your computer and use it in GitHub Desktop.
A quick guide on how to set up csharp projects

A quick guide on how to set up csharp projects with various features

scaffold

  1. create source directory (./src) that will hold the source code
  2. run dotnet new [options] (e.g. dotnet new --output ./src/<project name> web) to generate a new project
  3. remove unneeded generated files

set up docker

Dockerfile

To use a single dockerfile, but still get dotnet watch auto-reloading to work, use incremental builds.

Here, a dockerfile with two build steps is set up:

###############################################################################
# development build step
#
# here, the dotnet sdk (version 3.1, update if necessary) is used so any needed
# `dotnet` commands (e.g. `dotnet restore`) can be used during the build
###############################################################################
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
WORKDIR /app/

# restore dependencies before building
# separate step so docker can cache the dependencies
COPY ./MyProject.Api.csproj ./
RUN dotnet restore

# build in docker container
# still included in build-env step since we need the sdk to run dotnet-publish
COPY ./ ./
RUN dotnet publish -c Release -o ./out/ 

###############################################################################
# production build step
# 
# here the previously build app is copied from the previous step
# (see `--from=build-env`)
###############################################################################
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS runtime-env
WORKDIR /app/

COPY --from=build-env /app/out ./

ENTRYPOINT [ "dotnet", "MyProject.Api.dll" ]

reloading on change

If you want your application to be automatically reloaded every time it's changed, your app needs to be started with dotnet watch run.

doing this with the docker-compose file from above

In your docker-compose.yml:

  1. only build until reaching the required step (development/build-env)
  2. mount your source as volume and run dotnet watch run once the build step is completed:
services:
    # ...
    api:
        build:
            context: ./src/MyProject.Api # directory that contains the Dockerfile
            target: build-env
        volumes:
            - ./src/MyProject.Api
        command: dotnet watch run
    # ...

using target requires docker-compose file version 3.4 or greater

other ways to get hot-reloading

In the end, all that really matters is that your app is started using dotnet watch run.

How you do this is entirely up to you. The solution above is production ready though.

There's one more thing, however:

Directory.Build.props

based on a great article by Nate McMaster: https://natemcmaster.com/blog/2017/11/13/dotnet-watch-and-docker/

One of the most common problems with making Docker and Visual Studio (Code) work well is that the files in the obj/ and bin/ folders need to be different inside and outside the Docker container. If they overlap, you’ll get errors, including “Version for package ‘Microsoft.DotNet.Watcher.Tools’ could not be resolve”, issues with the runtime store, and more.

To resolve this, we will move the location of the obj/ and bin/ folders to sit next to the project directory, not inside it.

Add a file named Directory.Build.props to your project with these contents:

<Project>
  <PropertyGroup>
    <BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/../obj/</BaseIntermediateOutputPath>
    <BaseOutputPath>$(MSBuildProjectDirectory)/../bin/</BaseOutputPath>
  </PropertyGroup>
</Project>

(emphasis mine)

ASP.NET Core port forwarding

asp.net core won't receive connections by default in docker-compose, since it's listening to port 5000

to change that, do the following:

  1. Add an env var called ASPNETCORE_URLS and set it to http://+:80
  2. Map the wanted port of the application (e.g. 5000 if you want to be able to connect to your app using http://localhost:5000) to 80. (e.g. docker run -p 5000:80 ...)

set up debugging

based on a great article by Aaron Powell: https://www.aaron-powell.com/posts/2019-04-04-debugging-dotnet-in-docker-with-vscode/

prerequisite for debugging in vscode/vs ide: vsdbg

vsdbg is a debugger for vscode/vs ide with it, you can debug running .NET Core applications on remote hosts (e.g. docker containers), as long as it's installed on that host

to download and unzip it, run this bash command:

curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l <directory>

(the debugger will be downloaded and extracted to the <directory> specified above)

for docker-compose in VSCode

  1. extract vsdbg into it's own directory (e.g. ./vsdbg)
  2. if using git/other VCS, add that directory to your .gitignore/your-VCS's-ignore-file file
  3. add the directory (here ./vsdbg) as a volume (in the docker-compose.yml file):
# ...

services:
    api:
        # build:
        #     context: ./src/MyProject.Api/
        #     dockerfile: dev.Dockerfile
        # depends_on:
        #     - migrated_database
        volumes:
        #    - ./src/Rollingbets.Api:/usr/src/api
        # =====================================================================
            - ./vsdbg:/vsdbg
        # =====================================================================
        # ports:
        #     - 5000:80
        # environment:
        #     - ASPNETCORE_URLS=http://+:80
        # env_file:
        #     - .env
        #     - db.env

# ...

then create the vscode launch configuration:

    "configurations": [
        {
            "name": "Docker attach .NET Core",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickRemoteProcess}",

            // ================================================================
            // everything below needs to be changed based on your project
            // ================================================================
            "pipeTransport": {
                "pipeCwd": "${workspaceFolder}", 
                "pipeProgram": "docker-compose",
                "pipeArgs": ["exec", "-T", "<service name>"], // set the name of the docker-compose service
                "debuggerPath": "/vsdbg/vsdbg", // vsdbg needs to be available in the docker container! see above
                "quoteArgs": false // needed (otherwise errors will be thrown)
            },
            "sourceFileMap": {
                // ================================================================
                // needed by vscode! map source files here
                // ================================================================
                "/usr/src/api/": "${workspaceFolder}/src/<MyProject.Api>/"
            }
        }
    ]

set up migrations

a great tool for database migrations is flyway

for docker-compose

to set up flyway migrations for docker-compose, do the following:

  1. create a new directory for the migration files (e.g. migration/)
  2. create a new file called, for example, flyway.env (db.env would be a good alternative)
  3. set up your flyway.env and docker-compose.yml as below

flyway.env

FLYWAY_USER=<db username>

FLYWAY_PASSWORD=<db password>

# jdbc:<db name>://<docker-compose service name>:<db port>/<target database>
# for example:
FLYWAY_URL=jdbc:postgresql://database:5432/myCoolDatabase

FLYWAY_DRIVER=org.postgresql.Driver

docker-compose.yml

version: "3"

services:
    database:
        image: postgres:12
        volumes:
            - db-data:/var/lib/postgresql/data
        env_file:
            # contains database info such as database name, user, and password
            # ! this should likely match up with the information in flyway.env
            - postgres.env
        ports:
            - 5432:5432
    migrated_database:
        image: flyway/flyway
        depends_on:
            - database
        env_file:
            - flyway.env
        volumes:
            - ./<name of your local migration folder>:/flyway/sql # e.g. "./migration:/flyway/sql"
        command: migrate -connectRetries=60 # tries to connect 60 times before aborting (needed since postgresql won't start up immediately)
                                            # another alternative would be `wait-for-it.sh` (https://github.com/vishnubob/wait-for-it)
    api:
        depends_on:
            # services can now depend upon the migrated database
            - migrated_database
        # ...

#...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment