Microsoft’s ASP.NET Core MVC framework is a fast way to generate web applications using C#. Like any framework, its speed is made possible by the robustness of the tooling that goes into helping you generate your scaffolding, so you can focus on the business code, not the setup. Mainly, that means using Visual Studio. But for us in the GNU+Linux crowd, that’s a little bit of a problem: for better or worse, Visual Studio doesn’t run on Linux. So we will have to get around that by using the CLI tools that Visual Studio essentially uses for us behind the scenes.

Requirements

Packages:

  • .NET Core SDK, and two runtimes:
    • .NET Core runtime (a bit obvious)
    • ASP.NET Core runtime (imagine!)
  • NuGet, the .NET Package manager
  • SQLite3, because using PostgreSQL is too hard

CLI tools:

  • dotnet CLI tool, and two subtools:
    • dotnet-ef, the EntityFramework Core CLI tool for doing database operations
    • dotnet-aspnet-codegenerator, the glorified copy-pasta tool
  • nuget CLI tool, for installing the dotnet-* subtools (and doing general .NET package management stuff)
  • sqlite3

TL;DR

# Artix nerd reporting in
sudo pacman -S dotnet-sdk aspnet-runtime nuget sqlite sqlitebrowser

# I need the EntityFramework Core tool from nuget
dotnet tool install --global dotnet-ef

# I need the ASP.NET scaffolding code generator
dotnet tool install --global dotnet-aspnet-codegenerator
# Shorten that shit
alias asp-gen=dotnet-aspnet-codegenerator

# Don't just spawn shit in your home directory
cd ~/docs/dotnet  #or wherever you go for this kind of thing, I don't judge

# Spawn a new mvc app with Individual authentication
dotnet new mvc --name MyMVCApp --auth Individual
cd MyMVCApp

# Gimme teh codes
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

# Start defining a Person model
nvim Models/Person.cs

Editing Models/Person.cs:

using System;
using System.ComponentModel.DataAnnotations;

namespace MyMVCApp.Models
{
	public class Person
	{
		[Key]
		public Guid Id { get; set; }

		public string Name { get; set; }

		public string Email { get; set; }

		public Person() {}
	}
}

Back to the shell:

# Create the controller scaffolding
asp-gen controller -name PersonController --relativeFolderPath Controllers --model Person --dataContext ApplicationDbContext --useDefaultLayout --useSqlite

# Create the initial migration, which adds Data/Migrations/{timestamp}_InitialCreate.cs
dotnet ef migrations add InitialCreate  #shamelessly stolen command from Microsoft's docs

# Use the migration and create the database
dotnet ef database update

# Run the application
dotnet run

Now you can create and delete people at localhost:<port>/Person!

Getting the tools

You use Linux. That means you know how to install packages. The end! :D

As for myself, I use Artix Linux, so my package manager is pacman, and I could find several packages to install from my distro’s official repositories:

sudo pacman -S dotnet-sdk aspnet-runtime nuget sqlite sqlitebrowser

This gives me the .NET Core SDK and runtime, and the ASP.NET Core runtime. The .NET Core SDK comes with the dotnet CLI tool, and as you would suspect, the packages for NuGet and SQLite come with their respsective nuget and sqlite3 commands.

With these, you can get most of the way towards your running application. But I am missing dotnet-ef and dotnet-aspnet-codegenerator, which are not available through my package manager. That’s probably because they are NuGet packages. Since these are dotnet subtools, they can be installed using it.

First, dotnet-ef offers commands pertaining to using EntityFramework Core with .NET. It’s just a fancy title for “do database stuffs”. I found the following installation instructions from the EF Core CLI docs:

dotnet tool install --global dotnet-ef

Yay, I now have power over my database.

Now I want to be able to generate scaffolds for individual pieces of an MVC application, but I don’t have any of the fancy templates. One might imagine that dotnet new would be used for creating individual pieces, but no, that’s for general .NET architecture stuff, like entire projects and solutions. For pieces of an MVC project, you will need dotnet-aspnet-codegenerator. By digging down into the ASP.NET Core documentation, I was able to find
Web Apps > Advanced > aspnet-codegenerator
which showed me the following:

dotnet tool install --global dotnet-aspnet-codegenerator

This gives me the dotnet-aspnet-codegenerator tool that magically builds things for me so my fingers don’t get too tired. But with that in mind, this command’s name is entirely too long. I’m not going to type all that shit.

alias asp-gen=dotnet-aspnet-codegenerator

That’s better.

Creating the App

With all the toys installed, it’s time to play with them. I put my projects in ~/docs/<platform>/<project>, and you should do the same things as me since I am a genius. Or just navigate your shell to whatever directory you want to spawn your new MVC project in.

cd ~/docs/dotnet

Refer now to the .NET CLI documentation; With the dotnet new command, we want to use the mvc argument, because the webapp argument is equivalent to the razor argument. Maybe Razor pages are the new hotness, but it’s not what I was doing when I decided to get drunk on my own power and pretend to have knowledge worth passing on, so we’ll be using the more traditional ASP.NET MVC framework, which is available under dotnet new mvc. Add a name for the project with the -n option.

Observe the options for mvc and webapp projects. This will be a simple app with Individual authentication. That auto-generates the Data/ folder, and provides a base ApplicationDbContext.cs along with it. Set it with -au Individual.

The resulting command:

dotnet new mvc -n MyMVCApp -au Individual

The project can be found in a directory corresponding to its new name.

cd MyMVCApp

Look at all that code that just sprung out of a hole in the ground! This is beginning to look like Ruby on Rails, except business people actually use this.

Upon closer inspection…

This application is completely empty. To put stuff in it, we’ll have to manipulate the database. Because the -au Individual option argument was given, there’s already a Database Context for this application. We can see information about it using the EntitiyFramework CLI tool, dotnet-ef:

dotnet ef dbcontext --info

You’ll notice that the first thing this does is compile the project. At this point, that’s fine; if anything’s fucked up, it’s Microsoft’s fault. We haven’t written any code. After it compiles, it should give information like this:

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 6.0.2 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite:6.0.2' with options: None
Type: MyMVCApp.Data.ApplicationDbContext
Provider name: Microsoft.EntityFrameworkCore.Sqlite
Database name: main
Data source: app.db
Options: None

It uses the SQLite adapter by default, because SqlServer is Windows-only, and thus bad.

Include more magic!

We currently have an entire website with a database and everything. It’s just that there’s nothing in it. That’s normally okay; a database means it’s the users’ problem to give our website content, and we can sit back and collect ad revenue. But I want to build more functionality for my website out of thin air. That means I have to generate more code.

For those of you with your thinking caps on, you’ll have already made the connection between my use of the word “generate”, and the name of the dotnet-aspnet-codegenerator command (unless, like me, you have already erased its existence from your mind in favor of the much more sensible asp-gen alias). There’s just one problem: even dotnet-aspnet-codegenerator doesn’t know how to generate any code! What a travesty.

This is because there are no packages containing code templates for the CLI tool to copy-pasta from. If we ran asp-gen --help right now, it would give us some bullshit that ends with the following line:

No code generators are available in this project.Add Microsoft.VisualStudio.Web.CodeGeneration.Design package to the project as a NuGet package reference.

Thankfully, this tells us exactly what to do. The fact that it knows what needs to be done and can’t do it itself just proves that this is a tax funded venture. As with everything in life, I’ll do it myself:

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design

It’s free real estate

Now running asp-gen --help again shows us what kinds of code scaffolds we can generate at this point:

Available generators:
  minimalapi: Generates an endpoints file (with CRUD API endpoints) given a model and optional DbContext.
  identity  : Generates identity files.
  controller: Generates a controller.
  razorpage : Generates RazorPage(s).
  view      : Generates a view.
  area      : Generates an MVC Area.

I don’t know what an area is, so let’s ignore it and not investigate. Right now I’m interested in putting a new table in my database and creating a page where I can see and edit that table. The latter part of that task would be a controller. Upon reading the ASP.NET Code Generatord docs on Scaffolding a Controller, I am linked immediately to the page describing how to add a model to an ASP.NET MVC app. This indicates that before a controller can be scaffolded, a model for what data is being controlled needs to be defined.

Having to do actual work SUCKS

Fine, models live in Models/, so I’ll add Models/Person.cs. I’m just going to give a person a name and an email address, and index them on a Guid, which I’ll set as the primary key. I’m using Guid because it’s what the big bois use.

I need to add the System.ComponentModel.DataAnnotations namespace, so that I have access to the KeyAttribute, which marks the Id field as the primary key.

/* MyMVCApp/Models/Person.cs */
using System;
using System.ComponentModel.DataAnnotations;

namespace MyMVCApp.Models
{
	public class Person
	{
		[Key]
		public Guid Id { get; set; }

		public string Name { get; set; }

		public string Email { get; set; }

		public Person() {}
	}
}

Enough of this. Write my code for me, Microsoft

With the model in place, it’s time to completely rip off Microsoft’s tutorial steps and invoke the code generator (using the fancy alias, however).

asp-gen controller -name PersonController --relativeFolderPath Controllers --model Person --dataContext ApplicationDbContext --useDefaultLayout --useSqlite

The flags I used were obtained by running asp-gen controller --help, and about half of them are self-explanatory. I will just describe all of them like Microsoft did, because I am a plagiarist whore:

  • -name PersonController: names the controller PersonController. Conventionally, a controller should end with -Controller, because the item itself is the model. Notice the single dash for this option, as opposed to the rest of the options: the “long option” for this is --controllerName, but as one would suspect, that’s too damn long.
  • --relativeFolderPath Controllers: Tells it not to dump the PersonController.cs file in project root. No idea why that’s default but whatever.
  • --model Person: You get three guesses.
  • --dataContext ApplicationDbContext: This seems a bit out of left-field. It means that the database context to use here is the class ApplicationDbContext, which you can find under Data/ApplicationDbContext.cs. This flag is needed to specify what class will be used in the generated database migration file for actually forming the database table.
  • --useDefaultLayout: You can read.
  • --useSqlite: Ensures that the scaffolding will utilize the SQLite adapter instead of SqlServer, which we already know is of the devil.

Now if you have successfully fat-fingered those options like I did, fighting your own sanity against Microsoft’s option naming conventions and wondering why the hell they even bother to make short options that consist of more than one character, and ultimately rise above the odds with a successful command string, you’re in luck: you now have all the front-end files you need to CRUD people. There’s one problem remaining, however: you can’t. Why? Because the database still doesn’t know anything about the Person model.

More free shit!

This is where the EntityFramework Core CLI tool, dotnet-ef, comes in: it will create and run the migrations needed to populate the database with the Person table.

dotnet ef migrations add InitialCreate  #shamelessly stolen command from Microsoft's docs

This generates the migration file Data/Migrations/{timestamp}_InitialCreate.cs:

using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace MyMVCApp.Data.Migrations
{
    public partial class InitialCreate : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Person",
                columns: table => new
                {
                    Id = table.Column<Guid>(type: "TEXT", nullable: false),
                    Name = table.Column<string>(type: "TEXT", nullable: false),
                    Email = table.Column<string>(type: "TEXT", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Person", x => x.Id);
                });
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Person");
        }
    }
}

That’s all the code. I only wrote like 12 fucking lines and I get all this for free! Lucky me. Or you, if you followed along. Especially you, since I was here to guide you.

Make our ship go!

Time to put Microsoft’s hard work to use. Update the database and then run the application.

dotnet ef database update

dotnet run

Now you can visit your new MVC application on localhost at whatever port the tiny man in the console said! I used localhost:5090, but last time it was different so yours may vary.

Oddly, however, you can’t see anything about Persons. This is simply because the app’s index page hasn’t linked there. Visiting localhost:<port>/Person gets you to the Persons index, where you can play God to your heart’s content, creating and destroying lives at will.

Under-achievers go no further!

Something’s odd here: you can put any garbage value you want for an email! You can let the Person model validate the Email property directly, using a regular expression. Modify Models/Person.cs with these new lines:

 using System;
+using System.Text.RegularExpressions;
 using System.ComponentModel.DataAnnotations;
 
 namespace MyMVCApp.Models
 {
 	public class Person
 	{
+		private static Regex _validEmailPattern = new(
+				@"^[a-zA-Z0-9_-]+@(\w+\.)+\w{2,}$",
+				RegexOptions.Compiled
+				);
+
 		[Key]
 		public Guid Id { get; set; }
 
 		public string Name { get; set; }
 
-		public string Email { get; set; }
+		private string _email;
+		public string Email
+		{
+			get => _email;
+			set => _email = ValidatedEmail(value);
+		}
 
 		public Person() {}
+
+		private static string ValidatedEmail(string input)
+		{
+			if (! _validEmailPattern.IsMatch(input))
+				throw new Exception("Email pattern invalid");
+
+			return input;
+		}
 	}
 }

Run dotnet run again (it will rebuild for you) and automagically, you now have Email validation. The form won’t let you submit an email address that doesn’t pass the Regex match, because the MVC scaffolding’s exception handling catches your bullshit.

The end! :D