ASP.NET Core 2 comes with Razor Pages that allow developers to build simple web applications with less overhead compared to MVC. The emphazise 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 simple shoutbox application to illustrate how to build applications using Razor Pages.
Shoutbox application
We will build fully functional application you can use to further dig around and discover the secrets of Razor Pages.
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 Razor Pages application
Let’s start with new ASP.NET Core Razor Pages project. Yes, now there is new template for this.
Razor Pages projects have 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 controllers folder. Also there’s no folder for views.
New to Razor Pages? To find out more general overview of Razor Pages read my previous blog post Razor Pages with ASP.NET Core 2.
Database, data context and shoutbox entity
We will use SQL Server LocalDB as database and we go with Entity Framework Core code-first. First thing to do is to modify appsettings.json and add 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 create also simple entity class for shoutbox item. As we don’t create model mappings we have to use data annotations to let data context know how to create 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 database we need 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 framework-level dependency injection mechanism. We do it in ConfigureServices() method of Startup class.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ShoutBoxContext>(options => {
options.UseSqlServer(Configuration.GetConnectionString("ShoutBoxContext"));
});
services.AddTransient<ShoutBoxContext>();
}
Before using database we must ensure it is there and available. For this we add EnsureCreated() call to ends of Configure() method of 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 user interface for our simple shoutbox application.
Building shout list
Our simple application will show 100 latest shouts as a list on front page. This view is example of page with no code-behind file. All work is done on page itself. We will use view injection to get our data context to 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.Namestrong> | @shout.Time.ToString()<br />
@Html.Raw(shout.Message.Replace("\r\n", "
"))
p>
}
}
else
{
<p>No shouts... be the firts one!p>
}
div>
div>
<a href="AddShout">Add shouta>
In the end of page we have link to page where user can add 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 model for page is defined. Notice the @model directive in 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 PageModel class. We use constructor injection to get our data context to page model. The model we want to show on page is represented by Item property. BindProperty attribute tells ASP.NET Core that data from form must be bound to this property. Without it we must write code to extract values from request and do all the dirty work by ourselves. OnGet() method of page model is called when page is loaded using HTTP GET method and OnPost() is called when 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 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 mix of model and controller in MVC and its purpose is to provide separation of presentation and logic. We can use Razor Pages to build pages with or without backing model and it is completely up to us to decide which way to go. There’s no right or wrong until we stay in scope of small applications.
View Comments (4)
I can't believe it. It almost looks like java servlet code, onget, onpost etc..
We used to do this in PHP as well. My how the world has changed. Out with the new, in with the old.
It actually comes down to presentation layer pattern called Page Controller. This is why implementations are very similar on different platforms. More here by Martin Fowler: https://martinfowler.com/eaaCatalog/pageController.html
"ASP.NET Core 2 comes with Razor Pages that allow developers to build simple web applications with less overhead compared to MVC. "
A bit confusing. Razor pages is part of MVC and MS team members have indicated it does NOT have to be for simple apps
I indicate that based on my own experiences :) With MVC we have "bigger" architecture but also some more flexibility. If it is not some simple web based utility application I prefer to go with MVC.