Element 84 Logo

Create a Windows Application Without Windows


Azavea has been working with the Philadelphia Water Department (PWD) to support its Stormwater Billing program for over 10 years. We have built many different applications for them, like the Parcel Viewer and the Stormwater Credit Explorer. One of the first applications we ever built for them is an internal tracking tool PWD uses to manage the stormwater credits and appeals program. Given the age of the original app, we decided to rebuild it using a more modern software stack.

Philadelphia Water Department’s Stormwater Credit Explorer


Like a lot of government agencies, PWD’s internal software environment is based on Windows. The first generation of the tracking application was written in .NET/C#.  At that time, Azavea was primarily a C# shop. 10 years later, we have transitioned to using Python with Django for the back-end of our applications. Also, our team now has no other active clients that use Windows and no developers on Windows machines. In the past, we have maintained Windows virtual machines (VM) to do Windows work, but maintaining a VM is a bit of a burden, especially just for one project. In the previous iteration of this project, we had a physical machine running SQL server, which only provided one development database instance and one test database instance that were shared among all developers. Obviously, this was not a good practice. 

Another thing that has changed since we developed the first generation of the project is the development and release of .NET Core. .NET Core is an open-source programming framework that allows developers to use languages that were previously only available on Windows (like C#) to build cross-platform applications. Using .NET Core, we were able to build an application on Mac and Linux and deploy it to Windows. Below are some examples of how we adapted our normal development environment for the needs of this project.


We use Docker along with docker-compose to set up our development environments. In most cases, that means setting up a Django container for the back-end, a Postgres container for the database, and a React container for the front-end. For this application, we replaced the Django container with a .NET Core container and the Postgres container with a SQL container. Here’s a simplified version of the `docker-compose.yml` file:


   image: backend
     - "database"
     context: .
     dockerfile: ./Dockerfile
     - frontend:clientapp.service.internal
     - database:database.service.internal

   image: node:12-slim
   command: yarn start

   image: mcr.microsoft.com/mssql/server:2019-GA-ubuntu-16.04
     - sqlvolume:/var/opt/mssql


The `backend` Docker image referenced in the snippet above is based on the examples here and here.


We are able to get breakpoint debugging of the C# backend thanks to Visual Studio Code’s debugging capabilities. The vsdbg application also needed to be installed on the `backend` container. Here is the `launch.json` file used to configure debugging in Visual Studio Code:

    "version": "0.2.0",
    "configurations": [
            "name": "Debug .NET Core",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickRemoteProcess}",
            "sourceFileMap": {
                "/src": "${workspaceRoot}/"
            "pipeTransport": {
                "pipeCwd": "${workspaceRoot}",
                "pipeProgram": "docker",
                "pipeArgs": [
                "quoteArgs": false,
                "debuggerPath": "/vsdbg/vsdbg"

Our implementation is largely based on this blog post.


As mentioned above, developers shared a development and test database in the previous version of the app. Now, each developer has their own development database. For testing, a database is spun up on the fly by reusing the application database Docker image and provisioning scripts, but with a different database volume specified. We also override the `entrypoint` of the backend container with the CLI command to run tests instead of starting the development server.


   entrypoint: "dotnet test /src/App.Tests"

     - testsqlvolume:/var/opt/mssql


We can then run tests with the following commands:

 docker-compose -f docker-compose.yml -f docker-compose.test.yml up 

Using the `–abort-on-container-exit` flag will stop the containers once the tests finish, pass or fail.

Building for Production

In development, the app is built on a Linux container, but we ultimately need to deliver a Windows application for production. This is made easy by the dotnet command line tool, which we invoke in our production build script to get a cross-platform build that can be executed using the dotnet runtime in the client’s Windows environment.

dotnet publish "App.csproj" -c Release -o /app/publish

In conclusion

All of these techniques allow our team to successfully develop an application that is deployed to Windows while all of our developers are on Mac or Linux. Azavea uses open-source technology across the application stack, and with .NET Core, we now can develop applications for organizations that are restricted to a Windows-based environment and .NET technologies. This widens our team’s scope and allows us to help develop applications even if a client’s internal software environment is based on Windows. Go to our work page to read more about Azavea’s projects.