ASP.NET Core 5 with MVC: Project Structure and Development Cycle

Dan Gray

2020/09/20

Preamble

ASP.NET Core 5 is the future of cross-platform development. Microsoft has invested heavily in a feature-rich, performant platform, with excellent tooling for modern development.

MVC based architectures are still a solid choice for building scalable, manageable data-serving systems which allow a clear split between front-facing content and administration. Added to the fact that Razor Pages and SignalR can be incorporated directly into the project allows for a rich, and modern experience for administrators, developers and end-users.

Motivation

This overview, provides a skeleton for .NET Core MVC based development and is heavily derived from the teachings and courses of Bhrugen Patel of DotNet Mastery.

Development Cycle

The basic roadmap of project-development can be summarized into the following steps:

1. Add Model(s)

Classes define the structure of your data; and their properties become columns in your database.

It’s worth carefully thinking about, and sketching the data-model upon which your project is based. A minimum starting point are defining the fundamental properties, within your core entities.

namespace Aeon.Models
{
    public class Category
      {
          [Key]
          public int CategoryId {get; set; }
          
          public string Name {get; set; }
           
          public int DisplayOrder  {get; set; }
      }
}

2a. Define Connection String(s)

In appsettings.json add a default-connection;

  "ConnectionStrings" : {
    "DefaultConnection": "Server=(LocalDb)\\MSSQLLocalDB;Database=Aeon;Trusted_Connection=True;MultipleResultSets=True"
  }

2b. Set up DbContext

  • Firstly install the required package: MicrosoftEntityFrameworkCore.SqlServer

  • Inherent DbContext in your ApplicationDbContext.cs

namespace Aoen.Data
{
    public class ApplicationDbContext : DbContext
  {
    
  }
}
  • Pass options to DbContext
namespace Aoen.Data
{
    public class ApplicationDbContext : DbContext
    {
      public ApplicationDbContect(DbContextOptions<ApplicationDbContext> options) : base(options)
      {
      
      }
    }
}
  • Add properties for each model using DbSet
namespace Aoen.Data
{
    public class ApplicationDbContext : DbContext
    {
      public ApplicationDbContect(DbContextOptions<ApplicationDbContext> options) : base(options)
      {
      
      }
      
      public DbSet<Category> Category {get; set;}
      
    }
}
  • Add the Service to Startup.cs
  public void ConfigureServices(IServiceCollection services)
    {
      ....
      ......
      .........
      
      services.AddDbContext<ApplicationDbContext>(options => 
        options.UseSqlServer(
          Configuration.GetConnectionString("DefaultConnection")
          )
      );

      .........
      ......
      ....
      
    }

3. Push to the DB

Push the changes to the DB using Nuget Package Manager Console. Prior to the migrate, and update actions install MicrosoftEntityFrameworkCore.Tools.

  add-migration meanigfulDescriptionOfChanges

  update-database

4. Add a Controller

A Controller acts at the interfaces between data being called (via Models) and served (via Views). Each Model has a Controller, which can return multiple Views.

In the Controller folder, right click and Select “Add Controller” -> “MVC Controller - Empty” and give it a name, being careful to leave the Controller keyword as a suffix.

By default the newly created Controller will scaffold one method of type IActionResult, returning an Index View.

namespace Aeon.Controllers
{
    public class CategoryController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

By right-clicking on the method name, we can directly create the View via “Add View”. Choose “Razor View”, with “Empty Template” in the subsequent dialog box.

Finally add the appropriate routing within the <header> component of Shared/_Layout.cshtml.

<header>
  <nav>
    ....
    ......
    ........
    
    <li class="nav-item">
      <a class="nav-link text-dark" asp-area="" asp-controller="Category" asp-action="Index">Category</a>
    </li>
      
    ........  
    ......
    ....
  </nav>
</header>    

5. Use Dependency Injection (DI) to Deliver a DbContext Object and Retrieve Data

We can use DI to easily manage accessing and serving our data. Firstly we need an object of type DbContext. This can be created via a property.

namespace Aeon.Controllers
{
  public class CategoryController : Controller
  {
      private readonly ApplicationDbContext _db;
    
    ....
    .......
    ..........
  }
}

We can then use a constructor to populate this property. In the constructor we provide the name of the class or interface which is registered in our service container.

this object thus holds an instance of the DbContext, which can be passed to your local object as required

namespace Aeon.Controllers
{
  public class CategoryController : Controller
  {
      private readonly ApplicationDbContext _db;
    
      public CategoryController(ApplicationDbContext db)
      {
          _db = db;
      }
    
    ....
    .......
    ..........
  }
}

To retrieve data, we can use EF to deliver it via the database object (for example into a list of type IEnumerable).

namespace Aeon.Controllers
{
  public class CategoryController : Controller
  {
    ....
    .......
    ..........
    
    public IActionResult Index()
    {
      IEnemurable<Category> objList = _db.Category;
      return View(objList)
    }
    
  }
}

6. Add Further Action Methods

To further develop your application to fulfill basic CRUD operations, you need to add further Action Methods (for example “Create”, for both GET and POST methods).

// within CatgoryController.cs

namespace Aeon.Controllers
{

    ....
    ......
    ........

    // GET - CREATE
      public IActionResult Create()
      {
        return View();
      }

      
    // POST - CREATE
      [HttpPost]
      [ValidateAntiForgeryToken]
      public IActionResult Create(Category obj)
      {
        _db.Category.Add(obj);
        _db.SaveChanges();
        return RedirectToAction("Index");
      }
    
    ........
    ......
    ....
    
}

In addition you generate associated Views - which provide the UI for inputting/editing data etc.

When building these View(s) we can utilize tag-helpers such as ‘asp-controlller’ and ‘asp-create’ to manage defining our destination for these components.

// within View "Index.cshtml"


@model IEnumerable<Aeon.Models.Category>

<div class="container" p-3 >
    <div class="row pt-4">
        <div class="col-6" >
            <h2 class="text-primary">Category List</h2>
        </div>
        <div class="col-6">
            <a asp-controller="Category" asp-action="Create" class="btn btn-primary"> 
            Create New Category
            </a>
        </div>
    </div>
  
  ....
  .......
  .........

The complete UI for a “Create” -type View, will include Bootstrap styling, and also use tag-helpers extensively in labels, input-boxs, and buttons. Custom text can be substituted for class property defaults via Data Annotations.

@model Aeon.Models.Category


<form method="post" asp-action="Create">

    <div class="border p-3">
        <div class="form-group row">
            <h2 class="text-info pl-3">Add Category</h2>
        </div>
        <div class="row">
            <div class="col-8">
                <div class="form-group row">
                    <div class="col-4">
                        <label asp-for="Name"></label>
                    </div>
                    <div class="col-8">
                        <input asp-for="Name" class="form-control" />
                    </div>
                </div>
                <div class="form-group row">
                    <div class="col-4">
                        <label asp-for="DisplayOrder"></label>
                    </div>
                    <div class="col-8">
                        <input asp-for="DisplayOrder" class="form-control" />
                    </div>
                </div>
                <div class="form-group row">
                    <div class="col-8 offset-4 row">
                        <div class="col">
                            <input type="submit" class="btn btn-info w-100" value="Create"/> 
                        </div>
                        <div class="col">
                            <a asp-action="Index" class="btn btn-primary">Back</a>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-4">
                @* Keep this empty *@
            </div>
        </div>
    </div>
</form>