Razor Pages (not to be confused with Razor views when you’re using MVC) are a handy option for creating simple pages in your ASP.NET Core app.

Create a new Razor Page in Visual Studio and you get the Razor (markup) page (.cshtml) and a corresponding model class (.cs);

From here, you can expose properties on your model class for data that you want to display on the page.

Here’s a simple example of a Kanban board list page (based on the app we build in Practical ASP.NET Core MVC).

public class ListModel : PageModel
{
    [BindProperty]
    public BoardListModel Data { get; set; }

    public void OnGet()
    {
        // get your data here
    }

}

Then in your Razor markup you can bind to this model.

@page
@model Donatello.Pages.Board.ListModel

<h2>Board List</h2>

@foreach (var board in Model.Data.Boards) 
{
    @board.Name
}

Data is an arbitrary choice of name, the properties on your Model can be called anything you like.

One early concern about Razor Pages is that it brings us full circle, back to WebForms (or even “classic” ASP) and will inevitably result in everyone lumping all their code together in one place (bye bye separation of concerns).

In reality, it’s actually a bit simpler than that.

Razor Pages simplifies a few things like routing (the route is simply the folder path and the name of the file e.g. /board/list) but otherwise operates as a kind of controllerless MVC action.

Rather than route to a controller (and action), navigating to /board/list takes you to the relevant OnGet() method for that Razor Page.

Responsibility for writing properly separated code lives (as it always has) with you, the developer.

So whilst you could lump all your code together in the Razor Page .cs file.

public class ListModel : PageModel
{

    [BindProperty]
    public BoardListModel Data { get; set; }

    public void OnGet()
    {
        Data = new BoardListModel
        {
            Boards = new List<BoardListModel.Board>
            {
                new BoardListModel.Board
                {
                    Id = Guid.NewGuid(),
                    Name = "A board"
                }
            }
        };
    }

}

For the sake of this example we’re just using hardcoded data but in real life would call a database or some such.

You could equally use dependency injection or any other mechanism you fancy to keep this Model lean.

public class ListModel : PageModel
{
    IBoardService _service;

    public ListModel(IBoardService service)
    {
        _service = service;
    }

    [BindProperty]
    public BoardListModel Data { get; set; }

    public void OnGet()
    {
        Data = _service.ListAll();
    }

}

Or, if you’re a MediatR fan (like me)…

public class ListModel : PageModel
{
    IMediator _mediator;

    public ListModel(IMediator mediator)
    {
        _mediator = mediator;
    }

    [BindProperty]
    public BoardListModel Data { get; set; }

    public async Task OnGetAsync()
    {
        Data = _mediator.Send(new ListAllQuery());
    }

}

However you want to do it, you can still push your main business code/logic somewhere else and let your Razor Pages do what they’re good at, exposing data for your Razor markup to bind to.

Oh and just before you go, lest you think this whole controllerless action idea is new, here’s an interesting article from 2009.

Want to learn ASP.NET Core?

  • Want to learn ASP.NET Core but don't know where to start?
  • Don't have time to keep up with everything Microsoft is putting out?
  • Stuck on legacy apps when you want to build something new?

Pop your details in the boxes below and get regular updates from me about all things ASP.NET Core.