ASP.NET CORE MVC - Preparing for Production

ASP.NET CORE MVC TUTORIAL SERIES · PART 19

Preparing the MVC Application for Production

Separate development and production configuration, keep secrets out of source-controlled files, review HTTPS and database concerns, publish a Release build, and verify the exact files that will be deployed in Part 20.

Objective

By the end of Part 19, learners will understand how a development configuration differs from production, how ASP.NET Core selects environment-specific configuration, how environment variables can override settings, and how to produce a deployment-ready application directory with dotnet publish.

1. Verify the Part 18 Solution

Open the Xubuntu terminal:

cd ~/aspnet-mvc-tutorial
dotnet build ProductManagementTutorial.sln
dotnet test ProductManagementTutorial.sln

Continue only when the application builds and the automated tests pass.

2. Development and Production Are Different

DevelopmentProduction
Developer-friendly diagnosticsSafe, generic error responses
Local development databaseProtected persistent database location
Development configurationProduction configuration
Local testing credentialsServer-managed secrets/configuration
dotnet runPublished application hosted as a service
Source project ↓ Build and test ↓ Production configuration ↓ dotnet publish ↓ Published files ↓ Deployment

3. Understand ASP.NET Core Environments

ASP.NET Core commonly uses environment names such as:

Development
Staging
Production

The environment affects configuration and application behaviour. Your existing Program.cs already checks:

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

Therefore development can use developer-friendly behaviour while non-development environments use the generic production exception handler and HSTS.

4. Inspect the Current Environment Files

Open the application folder:

cd ~/aspnet-mvc-tutorial/ProductManagement
ls appsettings*.json

You should already have:

appsettings.json
appsettings.Development.json

ASP.NET Core can also load appsettings.Production.json automatically when the active environment is Production. citeturn968371search0

5. Create appsettings.Production.json

Create the file:

touch appsettings.Production.json
code appsettings.Production.json

Add:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
    }
  }
}

This example changes production logging without storing secrets in the file.

Do not place production passwords or secrets here.

Configuration files are easy to copy, publish or commit. Production secrets should come from a protected configuration source such as environment variables or an appropriate secret store. Microsoft specifically documents environment variables as a way to avoid storing secrets in source/local configuration files. citeturn968371search2

6. Understand Configuration Precedence

For this tutorial, think of the important configuration flow as:

appsettings.json ↓ appsettings.Production.json ↓ Environment variables ↓ Final configuration value

Environment variables can override values supplied by JSON configuration. citeturn968371search2

7. Review appsettings.json

Open:

code appsettings.json

Your file currently contains settings such as:

"ConnectionStrings": {
  "DefaultConnection": "Data Source=ProductManagement.db"
},

"AdminUser": {
  "Email": "admin@example.com"
}

The administrator email is configuration rather than a password, but the connection string may eventually contain sensitive credentials if the application moves to a server database.

8. Override Nested Configuration with Environment Variables

ASP.NET Core uses double underscores in environment-variable names to represent nested configuration keys.

For example:

export ConnectionStrings__DefaultConnection="Data Source=/var/lib/productmanagement/ProductManagement.db"
export AdminUser__Email="admin@example.com"

These correspond to:

ConnectionStrings:DefaultConnection
AdminUser:Email

9. Set the ASP.NET Core Environment

For a shell session, set:

export ASPNETCORE_ENVIRONMENT=Production

Verify:

echo $ASPNETCORE_ENVIRONMENT

The result should be:

Production
Important

Properties/launchSettings.json is a local development aid and isn't the mechanism you should depend on for configuring a deployed Linux service. Production deployment will set environment values outside the application project. Microsoft notes that launchSettings.json is used only on the local machine. citeturn968371search5

10. Review Production Error Handling

Open:

code Program.cs

Verify that this remains present:

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

Do not replace the production exception handler with detailed exception pages.

11. Review HTTPS and HSTS

Still in Program.cs, verify:

app.UseHttpsRedirection();

and the production block contains:

app.UseHsts();

Microsoft recommends HTTPS redirection and HSTS for production ASP.NET Core web applications, while noting that a reverse proxy may also handle these responsibilities. citeturn968371search5

12. Understand Reverse-Proxy Deployment

Part 20 will deploy the Linux application behind a reverse proxy.

Internet ↓ HTTPS ↓ Reverse Proxy ↓ Kestrel / ASP.NET Core ↓ MVC Application

The reverse proxy can terminate public HTTPS while Kestrel serves the application on an internal local port.

13. Review SQLite for Production

The tutorial application currently uses:

"Data Source=ProductManagement.db"

This is a relative path. Its exact location depends on the application's working directory.

For deployment, prefer an explicit writable data path such as:

Data Source=/var/lib/productmanagement/ProductManagement.db

This separates persistent data from published application binaries.

Do not treat the published folder as your only database backup.

The SQLite database contains persistent application data. Production operation requires deliberate file ownership, permissions and backup procedures.

14. Review File Permissions

The process running ASP.NET Core needs permission to read its application files and read/write the SQLite database.

Conceptually:

Application binaries ↓ read permission SQLite database ↓ read + write permission Log/service configuration ↓ appropriate restricted permissions

The actual Linux user and permission commands will be configured in Part 20.

15. Do Not Deploy Development Secrets

Before publishing, search the source tree for obvious accidental secrets:

cd ~/aspnet-mvc-tutorial/ProductManagement

grep -RniE "password|secret|apikey|api_key|token"     --exclude-dir=bin     --exclude-dir=obj     .

Review matches manually. Words such as Password may legitimately appear in Identity-related code or generated assets, so this is an audit aid rather than proof of a secret leak.

16. Build in Release Configuration

From the application folder:

dotnet build -c Release

Release builds are intended for deployment rather than interactive development.

17. Publish the Application

Remove any previous tutorial publish directory:

rm -rf ./publish

Then publish:

dotnet publish -c Release -o ./publish

dotnet publish compiles the application, processes its dependencies and writes the deployment assets to the chosen output directory. citeturn968371search9

18. Inspect the Published Output

ls -lah ./publish
find ./publish -maxdepth 1 -type f | sort

You should see files including the application assembly, dependency metadata, configuration files and static assets.

Typical important items include:

ProductManagement.dll
ProductManagement.deps.json
ProductManagement.runtimeconfig.json
appsettings.json
appsettings.Production.json
wwwroot/

19. Notice What dotnet publish Does Not Do

Publishing does not automatically:

  • configure a Linux service;
  • install Nginx;
  • create a public domain;
  • issue a TLS certificate;
  • choose secure filesystem permissions;
  • back up the database; or
  • apply production environment variables.

Those are deployment/operations tasks handled in Part 20.

20. Test the Published Application Locally

Stop any existing dotnet run process. Then:

cd ~/aspnet-mvc-tutorial/ProductManagement/publish

export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__DefaultConnection="Data Source=/home/xubuntu/aspnet-mvc-tutorial/ProductManagement/ProductManagement.db"

dotnet ProductManagement.dll

This launches the published application rather than the source project.

Why use the original database path for this local test?

The published folder is temporary deployment output. Using an explicit path avoids accidentally creating a second empty SQLite database simply because the working directory changed.

21. Verify Production Environment Behaviour

With the published application running, verify:

  • the Products page loads;
  • Identity login works;
  • the Dashboard works after login;
  • the Admin page remains role-protected;
  • a nonexistent route displays the friendly status page; and
  • the application is not showing detailed exception information publicly.

22. Verify Configuration from the Shell

In another terminal:

echo "$ASPNETCORE_ENVIRONMENT"
echo "$ConnectionStrings__DefaultConnection"

Remember that exported variables are scoped to the shell/process environment unless configured persistently by the operating system or service manager.

23. Re-run Automated Tests Before Deployment

Return to the tutorial root:

cd ~/aspnet-mvc-tutorial
dotnet test ProductManagementTutorial.sln -c Release

A production candidate should not be deployed knowingly with failing automated tests.

24. Production Readiness Checklist

AreaCheck
BuildRelease build succeeds.
TestsAutomated tests pass.
EnvironmentProduction configured outside local launch settings.
SecretsNo passwords/tokens stored in committed configuration.
ErrorsProduction exception handler enabled.
HTTPSHTTPS/reverse-proxy plan defined.
DatabasePersistent writable SQLite path planned.
PermissionsApplication/data ownership plan defined.
Publishingdotnet publish -c Release succeeds.
BackupDatabase backup procedure planned.

25. No EF Core Migration Is Required

Database note

Part 19 changes configuration and creates publish output. It does not alter the EF Core model, so no new migration is required.

26. Troubleshooting

The published application creates an empty database

A relative SQLite path is resolved from the application's working directory. Use an explicit database path through ConnectionStrings__DefaultConnection.

appsettings.Production.json seems ignored

Verify ASPNETCORE_ENVIRONMENT=Production is set for the process before the application starts.

Production environment variable is set but old app still behaves as Development

Environment variables are read when the process starts. Stop and restart the application after changing them.

dotnet publish succeeds but the website is not publicly accessible

Publishing only produces deployment files. Public hosting, service management, firewall/reverse proxy and TLS are Part 20 tasks.

27. Hands-On Exercise

  1. Create appsettings.Production.json.
  2. Set the environment to Production.
  3. Override the SQLite connection string with an environment variable.
  4. Build in Release.
  5. Publish to ./publish.
  6. Inspect the output.
  7. Run ProductManagement.dll from the publish folder.
  8. Verify that it uses the intended database.
  9. Run the Release test suite.

28. Knowledge Check

  1. What is ASPNETCORE_ENVIRONMENT?
  2. What is appsettings.Production.json?
  3. Why should production secrets not be stored in source-controlled JSON?
  4. How are nested configuration keys represented as environment variables?
  5. What does dotnet publish produce?
  6. Why can a relative SQLite path be risky during deployment?
  7. Why keep persistent data outside the publish directory?
  8. What is the purpose of HSTS?
  9. What is a reverse proxy?
  10. Why does Part 19 require no migration?

29. Part 19 Summary

  • distinguished Development and Production environments;
  • created appsettings.Production.json;
  • used environment variables to override nested configuration;
  • reviewed safe production secret handling;
  • reviewed production exception handling, HTTPS and HSTS;
  • planned a persistent SQLite data location;
  • built the application in Release configuration;
  • published the application with dotnet publish;
  • tested the published DLL locally; and
  • created a production-readiness checklist.

Appendix — Full Code and Commands for Final Verification

Appendix A — appsettings.Production.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
    }
  }
}

Appendix B — Program.cs Production Section

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute(
    "/Home/Status",
    "?code={0}");

app.UseHttpsRedirection();

Appendix C — Production Environment Example

export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__DefaultConnection="Data Source=/var/lib/productmanagement/ProductManagement.db"
export AdminUser__Email="admin@example.com"

Appendix D — Release Build and Publish

cd ~/aspnet-mvc-tutorial/ProductManagement

dotnet build -c Release

rm -rf ./publish

dotnet publish -c Release -o ./publish

ls -lah ./publish

Appendix E — Published Application Test

cd ~/aspnet-mvc-tutorial/ProductManagement/publish

export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__DefaultConnection="Data Source=/home/xubuntu/aspnet-mvc-tutorial/ProductManagement/ProductManagement.db"

dotnet ProductManagement.dll

Appendix F — Final Test Suite

cd ~/aspnet-mvc-tutorial

dotnet test ProductManagementTutorial.sln -c Release
Final Part 19 checkpoint

If the Release build succeeds, tests pass, production configuration is separated from development configuration, secrets are not embedded in committed files, the publish directory is generated successfully, and the published DLL can run against the intended SQLite database, Part 19 is complete.

Next: Part 20 — Deployment

The final tutorial will deploy the published application on Linux using Kestrel behind a reverse proxy, configure a system service, create the production data directory, apply migrations, set file permissions, inspect logs, and verify the deployed application.