How to Create Simple Shoutbox Using ASP.NET Core Razor Pages – Windows ASP.NET Core Hosting 2024 | Review and Comparison

How to Create Simple Shoutbox Using ASP.NET Core Razor Pages

ASP.NET Core 2 comes with Razor Pages that allow developers to build simple web applications with less overhead compared to MVC. The emphasis is on the word “simple” as Razor Pages doesn’t come with patterns suitable for bigger and more complex applications. For this, we have MVC that is flexible enough to build applications that will grow over years. This blog post uses a simple shoutbox application to illustrate how to build applications using Razor Pages.

Shoutbox Application

This post introduces how to build a simple and primitive shoutbox application using ASP.NET Core and Razor Pages. We will also use SQL Server LocalDb and Entity Framework Core code-first to make things more interesting. The goal of this post is to demonstrate how to use Razor Pages pages with and with-out a backing model.

We will build a fully functional application you can use to further dig around and discover the secrets of Razor Pages.

The source code of this post is available at my Github repository RazorPagesShoutBox. Currently, Visual Studio 2017 Preview 2 is needed to open and run the application.

Creating a Razor Pages Application

Let’s start with a new ASP.NET Core Razor Pages project. Yes, now there is a new template for this.

Razor Pages projects have a similar structure to MVC ones, but, as there are some differences, like Pages folder, and as Razor Pages doesn’t have controllers, we don’t have a controllers folder. Also, there’s no folder for views.

Database, Data Context, and Shoutbox Entity

We will use SQL Server LocalDB as our database and we will go with Entity Framework Core code-first. The first thing to do is to modify appsettings.json and add a connection string: I leave everything else like it is.

{
  "ConnectionStrings": {
    "ShoutBoxContext": "Server=(localdb)\\mssqllocaldb;Database=ShoutBoxContext;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  }

Let’s also create a simple entity class for our shoutbox item. As we don’t create model mappings we have to use data annotations to let the data context know how to create a database table.

public class ShoutBoxItem
{
    [Key]
    public int Id { get; set; }
    [Required]
    public DateTime? Time { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    public string Message { get; set; }
}

To communicate with the database we need a database context class too. We keep our database context as minimal as reasonably possible.

public class ShoutBoxContext : DbContext
{
    public ShoutBoxContext(DbContextOptions<ShoutBoxContext> options) : base(options)
    { }
    public DbSet<ShoutBoxItem> ShoutBoxItems { get; set; }
}

Finally, we need to introduce our database context to a framework-level dependency injection mechanism. We can do this with the ConfigureServices()method of the Startup class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddDbContext<ShoutBoxContext>(options => {
        options.UseSqlServer(Configuration.GetConnectionString("ShoutBoxContext"));
    });
    services.AddTransient<ShoutBoxContext>();
}

Before using the database, we must ensure it is there and available. For this, we add an EnsureCreated()call to the ends of the Configure() method of the Startup class.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }
    app.UseStaticFiles();
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
    app.ApplicationServices.GetRequiredService<ShoutBoxContext>()
                           .Database
                           .EnsureCreated();
}

Now we have everything we need to start building the user interface for our simple shoutbox application.

Building Shout List

Our simple application will show the 100 latest shouts as a list on the front page. This view is an example of a page with no code-behind file. All the work is done on the page itself. We will use it to get our data context to the page.

@page
@inject RazorPagesShoutBox.Data.ShoutBoxContext dataContext
@{
    ViewData["Title"] = "Home Page";
    var shouts = dataContext.ShoutBoxItems
                            .OrderByDescending(s => s.Time)
                            .Take(100)
                            .ToList();
}
<h2>@ViewData["Title"]</h2>
<div class="row">
    <div class="col-md-10">
        @if (shouts.Any())
        {
            foreach (var shout in shouts)
            {
                <p>
                    <strong>@shout.Name</strong> | @shout.Time.ToString()<br />
                    @Html.Raw(shout.Message.Replace("\r\n", "<br />"))
                </p>
            }
        }
        else
        {
            <p>No shouts... be the first one!</p>
        }
    </div>
</div>
<a href="AddShout">Add shout</a>

At the end of the page, we have a link to the page where the user can add a new shout.

Building New Shout Form

To let users shout, we create a separate page and this time we will use code-behind file where the model for the page is defined. Notice the @model directive in the page code.

@page
@model AddShoutModel
@{
    ViewData["Title"] = "Add shout";
}
<h2>@ViewData["Title"]</h2>
<div class="row">
    <div class="col-md-10">
        <form method="post">
            <div class="form-group">
                <label asp-for="Item.Name"></label>
                <input class="form-control" asp-for="Item.Name" />
                @Html.ValidationMessageFor(m => m.Item.Name)
            </div>
            <div class="form-group">
                <label asp-for="Item.Message"></label>
                <textarea class="form-control" asp-for="Item.Message"></textarea>
                @Html.ValidationMessageFor(m => m.Item.Message)
            </div>
            <input type="hidden" asp-for="Item.Time" />
            <button type="submit" class="btn-default">Shout it!</button>
        </form>
    </div>
</div>

All models that support pages are inherited from the PageModel class. We use constructor injection to get our data context to the page model. The model we want to show on the page is represented by Item property. The BindProperty attribute tells ASP.NET Core that data from the form must be bound to this property. Without it, we must write code to extract values from the request and do all the dirty work by ourselves. The OnGet() method of the page model is called when the page is loaded using the HTTP GET method and OnPost() is called when a POST was made.

public class AddShoutModel : PageModel
{
    private readonly ShoutBoxContext _context;
    public AddShoutModel(ShoutBoxContext context)
    {
        _context = context;
    }
    [BindProperty]
    public ShoutBoxItem Item { get; set; }
    public void OnGet()
    {
        if (Item == null)
        {
            Item = new ShoutBoxItem();
        }
        Item.Time = DateTime.Now;
    }
    public IActionResult OnPost()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
        Item.Id = 0;
        _context.ShoutBoxItems.Add(Item);
        _context.SaveChanges();
        return RedirectToPage("Index");
    }
}

It’s time to run the application and make some serious shouts!

Wrapping Up

Razor Pages provides us with a thinner model to build applications and it’s suitable for small applications. As it is part of ASP.NET Core MVC, it supports many features that come with MVC. The PageModel is like a mix of models and controllers in MVC and its purpose is to provide the separation of presentation and logic. We can use Razor Pages to build pages with or without a backing model and it is completely up to us to decide which way to go.