Build a standalone Windows executable in Visual Studio

Standalone Windows executables are compiled ahead of time (AOT) and distributed as three executable files (.exe, .dll, .runtimeconfig.json). This format is ideal for small executables called from the command line or from other applications.

To create an AOT Windows executable in Visual Studio 2022 or later

Start a new project

  • Create a C# console app

  • Name the app

  • Select .NET 8.0 or later framework, and Enable native AOT publish

  • To test the application we will write some temporary code in Program.cs
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Test executable started");

// Read the command line arguments into an array
string[] arguments = args;
Console.WriteLine($"Number of command line arguments {arguments.Length}");

if (arguments != null && arguments.Length > 0)
{
    var firstArgument = arguments[0];
    Console.WriteLine($"First argument = {firstArgument}");

    if (firstArgument == "ERROR" )
    {
        Console.WriteLine("Throw divide by zero exception");
        int a = 0;
        int b = 1 / a;
    }

}

Console.WriteLine("Test executable exited without error");  

Build the executable

Prerequisites

To be able to build, link and publish the standalone executable we need to install the “Desktop development with C++” Visual Studio workload.

  • In the Tools menu select Get Tools and Features

  • Install the Desktop development with C++ workload

Publish the executable

  • In the Solution Explorer right-click the project and select Publish

  • Select Folder as the target

  • Select Folder again for the specific target

  • Select the target location and click Finish

  • Close the dialog when the build has been completed

Running the standalone executable

By default the output is placed in the bin\Debug\net8.0 directory

To run the (not really) “standalone” application, we need the following three files in the same directory:

  • .exe
  • .dll
  • .runtimeconfig.json

Developing a secure .NET 8 (core) console application

In this post we will develop a .NET 8 console application which reads the configuration values from environment variables. This enables us deploy the application in an EKS cluster and read the config values from any secure secret storage.

The develoment environment

Docker configuration

To prevent Visual Studio from debugging in Docker containers

Debugging in Docker containers provides a stable, production like environment for your application, but larger applications can take a long time to build into containers. To stop Visual Studio debugging in Docker containers see Prevent Visual Studio 2022 from debugging in Docker. Official documentation is at Project warmup.

To allow Visual Studio to debug in Docker containers

If you want to debug in Docker containers to see how your application will run in production

Enable Docker Desktop execution

To be able to start Docker Desktop on Windows your user id has to be in the docker-users local user group. To add your user to the group

  • Open a command prompt as Administrator
  • Get the username:
whoami

Add the user to the docker-users user group:

net localgroup docker-users "your-user-id" /ADD

Log out and log in into Windows for the change to take effect.

Enable WSL (Windows Subsystem for Linux) for better performance with Docker Desktop

If Docker Desktop recommends using WSL instead of Hyper-V and WSL is not installed on your computer

  • Open an Administrator PowerShell window and execute
Enable-WindowsOptionalFeature -Online -FeatureName $("VirtualMachinePlatform", "Microsoft-Windows-Subsystem-Linux")

Restart the computer for the change to take effect.

You may need to install and enable the WSL2 Linux kernel. See Step 4 – Download the Linux kernel update package for details. Don’t forget to activate the WSL2 kernel with

wsl --set-default-version 2

Application settings

Create the application

  • Start Visual Studio 2022 community edition or newer
  • Create a new Console application using the C# programming language

Create the git repository

IMPORTANT!!!
To exclude the .env file and other temporary and build files from the Git commit create the .gitignore file

  • Open a terminal in the root directory of the repository and execute the commands
  • Create the .gitignore file
dotnet new gitignore
  • Create the Git repository
git init

Read the environment variables

Environment variables are the safest and most flexible way to store sensitive configuration values. This way those are never committed to source control.

Install the NuGet packages

To be able to access environment variables we need to install the following NuGet packages

  • Microsoft.Extensions.Configuration.EnvironmentVariables – to read environment variables
  • DotNetEnv – to load environment variables from .env files on the developer workstation

To install the packages from the command line, open a terminal in the project directory and execute

dotnet add package DotNetEnv
dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables

Create the local .env file

Create the .env file in the solution directory and add a key value pair

ENVIRONMENT=local

Write the code

Create the Settings.cs class to store the configuration values

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MY_NAMESPACE
{
    internal class Settings
    {
        public string? Environment { get; set; }
        public string? AppName { get; set; }
    }

    public class ConnectionStrings
    {
        public string? SQLServer { get; set; }
    }
}

Create the Configuration.cs class to read the configuration values form the environment variables

using DotNetEnv;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MY_NAMESPACE
{
    internal class Configuration
    {
        /// <summary>
        /// Reads the config values from the environment variables and returns the Settings object
        /// </summary>
        /// <returns></returns>
        public static Settings BuildAppSettings()
        {
            // Try to load the environment variables from the .env file for local development
            // Place the .env file in the root folder of the solution
            DotNetEnv.Env.TraversePath().Load();

            // Read the environment variables
            var environmentConfiguration = new ConfigurationBuilder()
                .AddEnvironmentVariables().Build();

            // Instantiate and populate the Settings object
            var settings = new Settings()
            {
                Environment = environmentConfiguration["ENVIRONMENT"],
                AppName = environmentConfiguration["APPNAME"]
            };

            // Return the Settings object
            return settings;
        }
    }
}

In the Program.cs file call Configuration to load the Settings object from the environment variables.

// See https://aka.ms/new-console-template for more information

using MY_APPLICATION;
using DotNetEnv;
using Microsoft.Extensions.Configuration;

// args is a string array that contains the command line arguments
string[] arguments = args;
Console.WriteLine($"Number of command line arguments {arguments.Length}");

// Read the configuration values
Settings settings = Configuration.BuildAppSettings();
Console.WriteLine($"Envronment: {settings.Environment}");
Console.WriteLine($"AppName: {settings.AppName}");
 

ERESOLVE could not resolve

When we try to refresh or install NPM packages there can be version conflicts between packages and the dependency requirements. The error message can vary, but it is similar to

npm error code ERESOLVE
npm error ERESOLVE could not resolve

To be able to install or refresh NPM packages

  • Delete the package-lock.json file
  • Delete the node_modules directory
  • Execute the npm install [MY_PACKAGE_NAME] commands one-by-one

These steps will most likely successfully reinstall the NPM packages, and if there is a conflict it will show in the response.

Migrate a PostgreSQL database to another database server

To migrate a PostgreSQL database to another database server

Back up the source database

  • Start pgAdmin4
  • Connect to the source database server
  • Right-click the dayabase and select Backup

  • On the General tab click the folder icon to set the backup file location and enter the file name. Set the format to Custom,

  • On the Data Options tab enable Pre-data, Data, Post-data

  • On the Query Options tab enable Use INSERT Commands

  • Click the Backup button

Restore the database on the target server

  • Connect to the target database server
  • Right-click the Databases item and select Create, Database…

  • Enter the name of the database and click the Save button

  • Right-click the name of the database and select Restore…

  • Click the folder icon to select the backup file and click the Restore button

Migrating Remix web applications to Node 20 and React Router v7

React Router, written by the Remix team, provides the missing routing services for React applications, used by 7 million websites. Remix propvides other features to make react application development more intuitive by providing

  • Automatic code splitting
  • Simplified data loading
  • Form Actions, Server actions
  • Simplified pending states
  • Optimistic UI
  • Server rendering
  • Static pre-rendering
  • React Server Components – RSC (coming soon)

The Remix team also added Vite, as it “has risen substantially in popularity, offering a fast dev experience, optimized builds, and a rich plugin ecosystem and authoring experience.”

As Upgrading from Remix to React Router states “React Router v7 is the next major version of Remix after v2”. React Router v7 now contains the functionality of @remix-run/node, @remix-run/cloudflare, etc.

The blog, Merging Remix and React Router explains the Remix team’s decisions, and give you the reason to make the change.

To upgrade from Remix to React Router 7

Upgrade Node to version 20

React Router v7 requires Node.js version 20 or higher, so at the time of writing most users have to upgrade Node.js.

If you used Homebrew to install Node.js, execute the commands

brew update       # Update Homebrew
brew tap --repair # If brew notifies you about broken references
brew upgrade      # If there are "Outdated Formulae"
brew upgrade node # Update Node.js to the latest stable version

Create a new Remix site using the React Router framework

Follow the instructions at React Router Installation.

At the time of writing these are the recommended steps:

Create the base application

npx create-react-router@latest MY_APP_NAME

If you want to keep your application in a subdirectory of your repository, select No for “Initialize a new git repository?”. Select Yes for “Install dependencies with npm?”

  • The process will create a folder matching the name of the application you specify, and will add that name to the package.json file as the “name” element.
  • The .gitignore file, and the .git directory will be placed in the application directory. As an application repository usually contains more than just the web application, like Terraform scripts, documentation, move the .gitignore file higher, remove the .git directory and recreate the repository with the git init command in the top level directory of your application. We also need to update the .gitignore file, and remove the leading slashes from all lines, as the node_modules, .react-router, and build directories are not in the root directory anymore.
  • Make sure at least the “Secrets” section is in the the top level .gitignore file to avoid committing secrets into Git.
.DS_Store

# Secrets
.env
.env*.local
*.pem
terraform.tfstate
terraform.tfstate.backup

# Terraform
.terraform/
.terraform.lock.hcl

# Node.js modules
node_modules/

# React Router
.react-router/
build/

Run the application

cd MY_APP_NAME
npm run dev

Navigate to http://localhost:5173 to view it in the browser.

Copy and update the source code

  • Copy the app/models, app/routes/, app/styles directories into the app folder of the new base application structure.
  • Execute the codemod to update the package references
npx codemod remix/2/react-router/upgrade

See Upgrading from Remix for more information on this step. Pay attention to the table at Update your dependencies (manual) which shows the old module imports to be replaced by the new imports.

Make your application work

Remove unused imports

Unused imports will trigger unnecessary error messages during the migration. To remove unused imports

  • In Visual Studio Code hover above an unused import
  • Click Quick Fix
  • Select Delete all unused imports

Remove imports

Remove these imports and if indicated import from a new module

ImportFromTo
LoaderArgsreact-router
ActionArgsreact-router
jsonreact-router
Route“./+types/home”“./+types/root”

Replace some lines

Use any instead of LoaderArgs and ActionArgs in the Loader and Action function signature. Use data instead of json to return values from the Loader to the client. Import data from react-router

- export async function loader({ request }: LoaderArgs) {
+ export async function loader({ request }: any) {

- export async function action({ request }: ActionArgs) {
+ export async function action({ request }: any) {

- return json({
+ return data({
    also add import { data } from "react-router";

Update your Make file

If you maintain a Makefile with the list of modules you need to install to make the application work, these modules are installed by React-Router during the application generation

install:
	npm i @react-router/node
	npm i @react-router/serve
	npm i isbot
	npm i react
	npm i react-dom
	npm i react-router

	npm -D @react-router/dev
	npm -D @tailwindcss/vite
	npm -D @types/node
	npm -D @types/react
	npm -D @types/react-dom
	npm -D react-router-devtools
	npm -D tailwindcss
	npm -D typescript
	npm -D vite
	npm -D vite-tsconfig-paths

Update the routing

Edit the app/routes.ts file and set the path for the index to point to your home page. Add all routing to this file.

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  index("routes/_index.tsx"),
  route("data_editor", "routes/data_editor.tsx")
  route("us/user_editor", "routes/us.user_editor.tsx")
] satisfies RouteConfig;

Run the application

Start the server with the npm run dev command. See Troubleshooting below for the most common error messages.

Install the missing modules

As you refresh the page, the Cannot find module ‘…’ imported from ‘…’ message will let you know which modules need to be installed with the npm install command. Update the install section of your Makefile to keep the list of necessary modules.

Separate files for server side and client side functions

To make the application work using the new framework and the Vite development tools, we have to make sure the client side files do not reference any modules that are not available in the browser. These include imports of our functions with database access.

The Remix compiler in the past could reference the server side and client side functions from common helper files. Vite needs separate files for the server side and client side.

If a client side file imports a function that contains references to functionality that is not available in the browser, like file system or database access the application compiles without error, the server starts without error, but we will find error messages in the console window of the browser.

Module “events” has been externalized for browser compatibility. Cannot access “events.EventEmitter” in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.

Uncaught ReferenceError: process is not defined
at node_modules/pg/lib/defaults.js

In this example our client side helper file only imported, but not called a function that accessed the PostgreSQL database.

Another example, when a function (logInfo) is imported from the server side file, but not used.

Solution: Delete the unused references

To import only a URL

RollupError]: “default” is not exported by ‘./index.css’ imported by “app/index.tsx”.

Style sheets are not compiled into the code, only the browser needs access to them to render the page. To reference style sheets, we only need to import the URL, so the compiler can make sure those are available and include them in the build. To import only the URL, add ?url to the end of the style sheet path.

import { LinksFunction } from "@remix-run/node";

-import styles from "./index.css";
+import styles from "./index.css?url";

Render UI only in the browser

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
or
React.jsx: type is invalid — expected a string (for built-in components) or a class/function (for composite components) but got: object.

Vite tries to render client side elements in the server. In the client side code render client side code only in the browser.

  // Check if the code is running in a browser
  const [isClient, setIsClient] = React.useState(false);
  React.useEffect(() => {
    setIsClient(true);
  }, []);
  
  # Render the UI
  return (
    <>
      {isClient ? (
        <Header/>
      ):null}

      {isClient ? (
        <div>
        ... The rest of the UI
        </div>
      ):null}

      {/* Display the Footer component */}
      {isClient ? (
        <Footer/>
      ):null}
      </>

  );

Custom image in the expandIcon

During the migration the compiler did not accept the Material UI icon library. You can use a custom PGN image for the expandIcon of the AccordionSummary:

<AccordionSummary
  expandIcon={<img src="/expand-more-down.png" />}
...

Replace Material UI controls with standard HTML controls

When I ran my application in a Docker container I started to get error messages related to Material UI controls. I replaced those with standard HTML elements. See troubleshooting elow for the full error messages.

Material UI controlHTML element
Boxdiv
AppBardiv
Buttoncustom dropdown menu function
Material UI icon libraryexpandIcon={<img src=”/MY_IMAGE.png” />}

Troubleshooting

Internal server error: [vite] The requested module ‘…’ does not provide an export named ‘default’

Enclose the function name in curly braces in the import statement. See Module … has no default export

[vite] Named export ‘json’ not found. The requested module ‘react-router’ is a CommonJS module, which may not support all module.exports as named exports.

Remove the json reference from the react-router import

import { json, redirect } from "react-router";
[vite] Named export 'GridColDef' not found. The requested module '@mui/x-data-grid' is a CommonJS module, which may not support all module.exports as named exports.

GridColDef is a type, import it with the type prefix. Quick actions can help

import {
  DataGrid,
  type GridColDef,
} from '@mui/x-data-grid';

(node:18) Warning: To load an ES module, set “type”: “module” in the package.json or use the .mjs extension.
(Use node --trace-warnings ... to show where the warning was created)
/app/node_modules/@mui/material/Box/index.js:1
export { default } from “./Box.js”;
^^^^^^

SyntaxError: Unexpected token ‘export’

When we run the application in a Docker container, we get the SyntaxError: Unexpected token ‘export’ error related to the Box.js. Remove references of <Box> from the application and replace those with <div>.

(node:18) Warning: To load an ES module, set “type”: “module” in the package.json or use the .mjs extension.

(Use `node –trace-warnings …` to show where the warning was created)
/app/node_modules/@mui/material/AppBar/index.js:1

export { default } from “./AppBar.js”;
^^^^^^

SyntaxError: Unexpected token ‘export’

When we run the application in a Docker container, we get the SyntaxError: Unexpected token ‘export’ error related to the AppBar.js. Remove references of <AppBar> from the application and replace those with <div>.

(node:18) Warning: To load an ES module, set “type”: “module” in the package.json or use the .mjs extension.

(Use `node –trace-warnings …` to show where the warning was created)
/app/node_modules/@mui/material/Button/index.js:1

export { default } from “./Button.js”;
^^^^^^

SyntaxError: Unexpected token ‘export’

When we run the application in a Docker container, we get the SyntaxError: Unexpected token ‘export’ error related to the Button.js. Remove references of <Button> from the application.

Module … has no default export

When a Node.js module import causes the

Module … has no default export
or
Internal server error: [vite] The requested module ‘…’ does not provide an export named ‘default’

error message, change the import statement, update the function name, and enclose the function name with curly braces:

# Replace
import jwt_decode from "jwt-decode";
# with
import { jwtDecode } from "jwt-decode"; 

This table contains the old and new function names

Old function nameNew function name
jwt_decodejwtDecode

Error: Dev server origin not set

We can set up a new Remix web application based on the Getting Started guide at Remix Quick Start

We will encounter a few surprises during the setup:

We need to allow legacy dependencies during the command:
npm i -D @remix-run/dev vite --legacy-peer-deps

When we try to run the application, we get the error message:

Error: Dev server origin not set

To be able to run the application in the development server set the NODE_ENV variable to production. In the terminal execute

NODE_ENV=production

As a permanent fix, always run the application with these commands

NODE_ENV=production
npx remix-serve build/server/index.js

sh: remix: command not found

When I tried to refresh Node.js packages, I have received the following error message:

sh: remix: command not found

The steps described at ERESOLVE could not resolve While resolving: remix-utils@8.1.0 cleared the cache and reinstalled all package versions to satisfy the package dependency requirements.

ETIMEDOUT: connection timed out, read [plugin browser-route-module]

When NPM, the Node Package Manager finds dependency version conflicts, we can help it to solve those by executing the steps described at ERESOLVE could not resolve While resolving: remix-utils@8.1.0

After the package reinstallation, when I tried to run my application, I have received the error message:

ETIMEDOUT: connection timed out, read [plugin browser-route-module]

I had to reboot my computer to be able to run the application again. Most likely that closed running processes and cleared memory caches of old packages.