Friday, December 30, 2022

Creating a master/detail web application with ASP.NET Razor Pages

In this tutorial, you will create a master/detail web application using ASP.NET Razor Pages. The data used will be read from your operating system. Specifically, we will query the local operating system with the number of processes that are running and then display details about each process. We shall use Visual Studio Code for our editor.

Source Code: https://github.com/medhatelmasry/OsProcess
Companion video: https://youtu.be/u_CT0aUz4lc

Getting started

Inside a working directory, run the following command in a terminal window to create a Razor Pages web application in a folder names OsProcess:

dotnet new razor -o OsProcess

Change into the newly created directory with:

cd OsProcess

To run the application in watch mode type:

dotnet watch

The following web app will appear in your default browser:



Our objective is to display all the processes that are running in your operating system.

Displaying processes on the home page

Stop the web server by hitting CTRL C in the terminal window. Then, open the project in VS Code by typing the following in the same folder:

code .

Open Pages/Index.cshtml.cs in the editor.  Add the following using statement at the top of the IndexModel class:

using System.Diagnostics;

Find the OnGet() method and add the following code into it:

Process[] processes = Process.GetProcesses();
ViewData["P"] = processes;

The above code uses the Process class’s static method GetProcesses() to read all the processes that are running in your operating system. The next step is to display this information in the view. Open Pages/Index.cshtml in the editor. Replace the contents of Pages/Index.cshtml with the following:

@page
@using System.Diagnostics
@model IndexModel
@{
    ViewData["Title"] = "OS Processes";
    Process[]? processes = ViewData["P"] as Process[];
}

<div class="text-center">
    <h1 class="display-4">@ViewData["Title"]!</h1>
        @foreach (var item in processes!)
        {
            @if (item.ProcessName.Trim().Length > 0)
            {
                <p><a asp-page="./Details" asp-route-id="@item.Id">@item.ProcessName</a></p>
            }
        }
</div>

The above code assigns a ViewData object, passed from the code behind, to an array of Process objects. It then displays the array items in a series of links.

Run the app in watch mode by typing the following command:

dotnet watch

This is what I see on my mac computer. You will experience different data on your computer depending on your operating system and the background applications that are currently running on your computer.



When you click on any of the links, you will notice that it does nothing. Let’s analyze the anchor tag:

<a asp-page="./Details" asp-route-id="@item.Id">@item.ProcessName</a>

This suggests that there is a ./Details page that is missing. Let us add this page by typing the following scaffolding command in the terminal window:

dotnet new page --namespace OsProcess.Pages --name Details --output Pages

The above creates files Pages/Details.cshtml and Pages/Details.cshtml.cs.


Open Pages/Details.cshtml.cs in the editor. Just like we did before, add the following using statement at the top of the DetailsModel class:

using System.Diagnostics;

Also, in Pages/Details.cshtml.cs, replace the OnGet() method with the following:

public void OnGet(int id) {
    ViewData["P"] = Process.GetProcessById(id);
}

The above code retrieves details about a specific process from its ID and feeds the result into the ViewData dictionary with key P.

Open Pages/Details.cshtml in the editor. Replace the contents of this file with the following:

@page
@using System.Diagnostics
@model OsProcess.Pages.DetailsModel
@{
    Process? process = ViewData["P"] as Process;
    ViewData["Title"] = $"Process with ID={process!.Id}";
}

<h1>@ViewData["Title"]</h1>

<div>
    <hr />
    <table class="table table-striped">
        <tr>
            <td>Process Name</td><td>@process.ProcessName</td>
        </tr>
        <tr>
            <td>Number of threads</td><td>@process.Threads.Count</td>
        </tr>
    </table>
</div>
<div>
    <a asp-page="./Index">Back to List</a>
</div>

The above code displays the details of a specific process. These do not, by any means, represent all the properties of a process. In the interest of simplicity, we are only looking at four properties. Run the web app in your browser and you will see the following behavior:


Using property approach

If you do not wish to use ViewData or ViewBag objects, there is another better way of achieving the same outcome. Let’s starts with the master page involving Pages/Index.cshtml and Pages/Index.cshtml.cs. Open Pages/Index.cshtml.cs in the editor and make the following highlighted changes to it:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;

namespace OsProcess.Pages;

public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;

    public IList<Process> Processes { get;set; } = default!;

    public IndexModel(ILogger<IndexModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        Processes = Process.GetProcesses().ToList();
    }
}

In the above code, we set the value of the class’s Processes property with the list of processes. This property can get directly accessed from the view file.

Similarly, update Pages/Index.cshtml.cs so it looks like this:

@page
@using System.Diagnostics
@model IndexModel
@{
    ViewData["Title"] = "OS Processes";
}

<div class="text-center">
    <h1 class="display-4">@ViewData["Title"]!</h1>
        @foreach (var item in Model.Processes)
        {
            @if (item.ProcessName.Trim().Length > 0)
            {
                <p><a asp-page="./Details" asp-route-id="@item.Id">@item.ProcessName</a></p>
            }
        }
</div>

Note that processes can be accessed with Model.Processes in the view. The latest solution is much more elegant and avoids the use of ViewData to pass data from the code behind file to the view. If you run the application again, you will notice that it behaves just like before.

One last thing we can do is to use the Property approach also in the Details page. Here is the code for Details.cshtml.cs & Details.cshtml:

Details.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;

namespace OsPorocess.Pages
{
    public class DetailsModel : PageModel
    {
        public Process SingleProcess { get; set; } = default!;
        public void OnGet(int id)
        {
            //ViewData["P"] = Process.GetProcessById(id);
            SingleProcess = Process.GetProcessById(id);
        }
    }
}

Details.cshtml

@page
@using System.Diagnostics
@model OsPorocess.Pages.DetailsModel
@{
    //Process? process = ViewData["P"] as Process;
    ViewData["Title"] = $"Process with ID={Model.SingleProcess.Id}";
}

<h1>@ViewData["Title"]</h1>

<div>
    <table class="table table-striped">
        <tr>
            <td>Process Name</td>
            <td>@Model.SingleProcess.ProcessName</td>
        </tr>
        <tr>
            <td>Number of threads</td>
            <td>@Model.SingleProcess.Threads.Count</td>
        </tr>
    </table>
    <div>
        <a asp-page="./index">Back to list</a>
    </div>
</div>

The same approach for master/detail data can be used in a variety of scenarios.

No comments:

Post a Comment