Promoting SoC Through Application Layering

Creating a layered design in an application is a fundamental element of modern software architecture. The goal is to promote the Separation of Concerns (SoC) design principle. Separation of Concerns The ideas behind SoC date back to Dijkstra's 1974 paper "On the role of scientific thought" . In computer science, separation of concerns (sometimes abbreviated as SoC) is a design principle for separating a computer program into distinct sections. Each section addresses a separate concern, a set of information that affects the code of a computer program. A concern can be as general as "the details of the hardware for an application", or as specific as "the name of which class to instantiate". A program that embodies SoC well is called a modular program. Modularity, and hence separation of concerns, is achieved by encapsulating information inside a section of code that has a well-defined interface. - Wikipedia SoC is a broad design principal th...

Razor Page with Multiple Get/Post Handlers

Razor Pages are great for creating simple web applications. By default, a Razor Page should be compact, this often means a single Get and a single Post handler. This is usually sufficient for developing a basic CRUD style application. If your application needs more complex functionality, you may want to consider developing a Blazor or MVC ASP.NET application.

Razor Page with Multiple Gets and Posts Screenshot

To deal with the scenario where a Razor Page needs multiple get/post handlers, ASP.NET provides the asp-page-handler action on the form tag. This can be combined with routing to enable multiple get/post handlers.

First, take a Razor Page and configure the routing to handle multiple get/post handlers as follow:

@page "{handler?}"

To create a form that calls the get/post handler, we can add the asp-net-handler action to the form as follows:

<form method="get" asp-page-handler="SortByKey">
    <button type="submit" class="btn btn-outline-light btn-sm ms-2">Sort</button>
</form>

The get/post handler code looks like:

public void OnGetSortByKey() { }

There is some "magic" in the naming conventions, in that the code method must start with OnGet or OnPost (depending on the form method), followed by the value you put in the asp-net-handler action.

Full Sample

Below is the full Razor Page code example:

@page "{handler?}"
@model IndexModel
@{
    ViewData["Title"] = "Gadgets";
}

<div class="text-center mb-4">
    <h1 class="display-4">Gadgets</h1>
    <i>Razor Page with numerous get/post actions.</i>
</div>

<div>
    <table class="table">
        <thead class="table-dark">
            <tr>
                <th scope="col">
                    <div class="d-flex flex-row align-items-center">
                        Key
                        <form method="get" asp-page-handler="SortByKey">
                            <button type="submit" class="btn btn-outline-light btn-sm ms-2">Sort</button>
                        </form>
                    </div>
                </th>
                <th scope="col">
                    <div class="d-flex flex-row align-items-center">
                        Type
                        <form method="get" asp-page-handler="SortByType">
                            <button type="submit" class="btn btn-outline-light btn-sm ms-2">Sort</button>
                        </form>
                    </div>
                </th>
                <th scope="col">
                    <div class="d-flex flex-row align-items-center">
                        Usage Instructions
                        <form method="get" asp-page-handler="SortByUsageInstructions">
                            <button type="submit" class="btn btn-outline-light btn-sm ms-2">Sort</button>
                        </form>
                    </div>
                </th>
                <th></th>
                <th></th>
            </tr>
        </thead>
        @foreach (var gadget in Model.Gadgets)
        {
            <tr>
                <td>@gadget.GadgetKey</td>
                <td>@gadget.GadgetType</td>
                <td>@gadget.UsageInstructions</td>
                <td>
                    <form method="post" asp-page-handler="Details">
                        <input type="hidden" name="gadgetKey" value="@gadget.GadgetKey" />
                        <button type="submit" class="btn btn-secondary">View</button>
                    </form>
                </td>
                <td>
                    <form method="post" asp-page-handler="Delete">
                        <input type="hidden" name="gadgetKey" value="@gadget.GadgetKey" />
                        <button type="submit" class="btn btn-danger">Delete</button>
                    </form>
                </td>
            </tr>
        }
    </table>
</div>

Here is the code associated to the above Razor Page:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPageWithMultipleGetPost.Pages
{
    public class IndexModel : PageModel
    {
        public List<Gadget> Gadgets = new List<Gadget>();

        public IndexModel()
        {
        }

        /// <summary>
        /// Default Get
        /// </summary>
        public void OnGet()
        {
            Gadgets = GetGadgets();
        }

        /// <summary>
        /// Get data sorted by GagdetKey
        /// </summary>
        public void OnGetSortByKey()
        {
            Gadgets = GetGadgets().OrderBy(f => f.GadgetKey).ToList();
        }

        /// <summary>
        /// Get data sorted by GadgetType
        /// </summary>
        public void OnGetSortByType()
        {
            Gadgets = GetGadgets().OrderBy(f => f.GadgetType).ToList();
        }

        /// <summary>
        /// Get data sorted by UsageInstructions
        /// </summary>
        public void OnGetSortByUsageInstructions()
        {
            Gadgets = GetGadgets().OrderBy(f => f.UsageInstructions).ToList();
        }

        /// <summary>
        /// delete the Gadget from the datastore
        /// </summary>
        /// <param name="gadgetKey"></param>
        public void OnPostDelete(string gadgetKey)
        {
            // remove a Gadget temporarily
            Gadgets = GetGadgets().Where(f => f.GadgetKey != gadgetKey).OrderBy(f => f.GadgetKey).ToList();

            System.Diagnostics.Debug.WriteLine($"Delete GadgetKey {gadgetKey}");
        }

        /// <summary>
        /// handle Detail request... either display popup or navigate to detail page
        /// </summary>
        /// <param name="gadgetKey"></param>
        public void OnPostDetails(string gadgetKey)
        {
            // since this method doesn't do anything, just reset the Gadget list
            Gadgets = GetGadgets();

            System.Diagnostics.Debug.WriteLine($"Show Details for GadgetKey {gadgetKey}");
        }

        /// <summary>
        /// Generate an in-memory list of Gadgets
        /// </summary>
        /// <returns></returns>
        private List<Gadget> GetGadgets()
        {
            return new List<Gadget>() {
                new Gadget()
                {
                    GadgetKey = "1",
                    GadgetType = "Type Z",
                    UsageInstructions = "Assemble Widgets with Gadget Z."
                },
                new Gadget()
                {
                    GadgetKey = "2",
                    GadgetType = "Type X",
                    UsageInstructions = "Use Widgeths with Gadget Z."
                },
                new Gadget()
                {
                    GadgetKey = "3",
                    GadgetType = "Type Y",
                    UsageInstructions = "Test Widgets with Gadget Y."
                }
            };
        }
    }
}

NOTE: This example abuses forms and is very fragile, with the goal of keeping the example simple.

This example was created using Visual Studio 2022 running an ASP.NET Core Web App project using IIS Express.

Source code: https://github.com/jharrell-bits/RazorPageWithMultipleGetPost