Creating .NET solutions on a Linux system has always presented difficulties due to Microsoft Visual Studio’s dependency on the Windows operating system. My experience with numerous .NET projects led me to investigate the feasibility of .NET development on Linux. This straightforward tutorial, centered around an ASP.NET MVC application integrated with SQL Server, aims to illustrate the streamlined and potent nature of .NET development on my preferred operating system.
Setting up the Development Environment
Our initial step involves ensuring that the .NET tools and SDK, tailored to our specific Linux distribution, are properly installed. We can accomplish this using Microsoft’s standard guide.
My ideal development environment comprises a windowed integrated development environment (IDE), a robust database management and query tool, the database itself, and tools for both building and deploying applications. I achieve robust functionality and a pleasant coding experience using the following tools:
- IDE: Visual Studio Code
- Database management and query tool: DBeaver
- Database: Microsoft SQL Server (Linux Installation)
- Build tools: .NET SDK Command Line Interface (CLI)
- Virtual machine and containers: Docker
Prior to proceeding with our sample application, ensure that these tools are correctly installed.
Project Structure Setup
Our sample application will demonstrate ASP.NET development and functionality by simulating various use cases for a hypothetical shoe store inventory management system. As is standard practice with new .NET applications, we’ll begin by creating a solution and adding a project to it. The .NET SDK CLI tools can be used to scaffold our new solution:
| |
Next, let’s create an ASP.NET project that includes a clearly defined main class for simplicity, as this structure is likely familiar to ASP.NET developers. We’ll use the MVC pattern to create our project:
| |
Let’s now integrate the project into the solution:
| |
We now have a basic solution and its associated ASP.NET project. Before moving on, it’s crucial to verify that everything builds successfully:
| |
A good development practice is to encapsulate key services and the application runtime within Docker containers. This approach enhances both deployment ease and portability. To support our application, let’s create a simple Docker container.
Making the Application Portable
Docker images typically reference a parent Docker image as a foundation, inheriting essential components like the operating system and fundamental solutions, such as databases. Adhering to this Docker best practice, we’ll create both a Dockerfile and a Docker Compose file for proper service configuration. We’ll utilize Microsoft-published parent images as our base. Additionally, we’ll employ Docker stages to minimize our image size. Stages enable us to utilize the .NET SDK during application building, ensuring that the ASP.NET runtime is only required during application execution.
Create the Shoestore.mvc Dockerfile with the following code:
| |
Next, we’ll create the docker-compose.yml file within our solution’s root directory. Initially, it will only reference our application service’s .Dockerfile:
| |
Let’s also configure our environment with a .dockerignore file to guarantee that only the build artifacts are copied to our image.
With our application service stubbed in and its execution environment ready, our next task is to create our database service and integrate it into our Docker configuration.
Setting up the Database Service
Integrating Microsoft SQL Server into our Docker configuration is straightforward, especially because we’re using a Microsoft-provided Docker image without modifications. Append the following configuration block to the bottom of the docker-compose.yml file to configure the database:
| |
In this configuration, ACCEPT_EULA prevents installation interruptions, and the ports setting allows the default SQL Server port to pass through without any translation. Our Compose file now encompasses both our application and database services.
Before we customize the application code, let’s ensure that our Docker environment is functioning as expected:
| |
If no errors occur during startup, our basic sample application should be accessible through a web browser at the local address http://localhost:8080.
Utilizing Code Generation Tools
Now comes the engaging part: customizing the application code and ensuring that application data is persistently stored in the Microsoft SQL Server database. We’ll utilize both the Entity Framework (EF) and .NET SDK tools to establish a connection between the application and the database. These tools will also help us scaffold the application’s model, view, controller, and EF-required configuration.
Before we can specify the necessary tools, we need to create a tool-manifest file:
| |
Let’s add the EF and SDK tools to this file using these simple commands:
| |
To confirm that these tools are installed correctly, run dotnet ef. The appearance of a unicorn indicates a successful installation. Next, run dotnet aspnet-codegenerator to test the ASP.NET tools; the output should display a general CLI usage block.
With our tools ready, we can start building our application.
MVC: The Model
The first step in building our application is creating the model. Since this model will be incorporated into the database later, we’ll add the MS SQL Server and EF packages to our project:
| |
Next, we’ll create an EF database context object. This object defines which models are added to the database and provides our code with easy access to query data from the database.
Create a Data directory to store the EF-specific code and create the ApplicationDBContext.cs file with the following content:
| |
Next, we’ll configure the database connection string, ensuring it matches the credentials defined in our Dockerfile. Set the content of Shoestore/Shoestore.mvc/appsettings.json to the following:
| |
With the database connection string configured and the database context coded, we can now write our application’s Main function. We’ll include database exception handling to simplify system debugging. Additionally, due to a .NET bug in the generated code that causes the Docker container to serve views incorrectly, we need to add specific code to our view service configuration. This code will explicitly define the file paths to our view location within the Docker image:
| |
Within the same file, navigate to the IsDevelopment if statement and add an else statement with the following code to include a database migration endpoint in our system when it’s in development mode:
| |
Let’s run a quick test to ensure that the newly added packages and source code edits compile without errors:
| |
Now, let’s populate the model with the necessary fields by creating the Shoestore.mvc\Models\Shoe.cs file:
| |
EF generates SQL queries based on the associated model, its context file, and any EF code within our application. SQL results are then translated and returned to our code as needed. By adding our Shoe model to our database context, EF will know how to translate data between MS SQL Server and our application. Let’s do this in the database context file, Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs:
| |
Finally, we’ll use a database migration file to integrate our model into the database. The EF tool generates a migration file tailored to MS SQL Server based on the database context and its associated model (i.e., Shoe):
| |
We’ll hold off on running our migration until we have a controller and view in place.
MVC: The Controller and View
We’ll create our controller using the ASP.NET code generation tool. This powerful tool requires specific helper classes. We’ll use the Design style packages for the basic controller structure and its integration with EF. Let’s add these packages:
| |
Creating our default controller is now as simple as invoking the following command:
| |
When the code generator creates our controller, it also generates a simple view for that controller. With the foundation of our MVC structure in place, we’re ready to test everything.
Migration and Application Testing
EF migrations are usually straightforward, but Docker integration adds complexity. As a bonus exercise, you can delve into the intricacies of making these migrations work within our Docker solution. For now, our focus is on running the migration.
The repository contains all the necessary configuration and migration files. Clone the complete project to your local machine and execute the migration:
| |
The docker composer operation builds our application, executes the migration, and launches our ASP.NET application with the .NET runtime. Access the running solution by visiting http://localhost:8080/Shoes.
While our application interface is basic, it demonstrates functionality across all tiers, from the view down to the database. Refer to the full repository for a comprehensive overview of our solution.
.NET on Linux: A Practical Reality
.NET on Linux is more than just a possibility; it’s a highly viable combination of language, runtime, and operating system. While developers accustomed to Visual Studio might be less familiar with using the full potential of the .NET CLI, these tools are both effective and powerful.
The Toptal Engineering Blog thanks Henok Tsegaye for his review of the code samples presented in this article.