Introduction To Razor Pages in ASP NET Core
Introduction To Razor Pages in ASP NET Core
NET Core
Sursa:
https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-3.1&tabs=visual-studio
Razor Pages can make coding page-focused scenarios easier and more productive than using
controllers and views.
If you're looking for a tutorial that uses the Model-View-Controller approach, see Get started
with ASP.NET Core MVC.
This document provides an introduction to Razor Pages. It's not a step by step tutorial.
If you find some of the sections too advanced, see Get started with Razor Pages (link:
https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start?view=aspnetcore-
3.1&tabs=visual-studio).
Prerequisites
Visual Studio
Visual Studio Code
Visual Studio for Mac
Visual Studio 2019 with the ASP.NET and web development workload
.NET Core 3.0 SDK or later
Razor Pages
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
@page
<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>
The preceding code looks a lot like a Razor view file used in an ASP.NET Core app with
controllers and views. What makes it different is the @page directive. @page makes the file into
an MVC action - which means that it handles requests directly, without going through a
controller. @page must be the first Razor directive on a page. @page affects the behavior of
other Razor constructs. Razor Pages file names have a .cshtml suffix.
A similar page, using a PageModel class, is shown in the following two files.
The Pages/Index2.cshtml file:
@page
@using RazorPagesIntro.Pages <!-- Namespace for Index2Model class -->
@model Index2Model
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
namespace RazorPagesIntro.Pages
{
public class Index2Model : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
By convention, the PageModel class file has the same name as the Razor Page file
with .cs appended. For example, the previous Razor Page is Pages/Index2.cshtml. The file
containing the PageModel class is named Pages/Index2.cshtml.cs.
The associations of URL paths to pages are determined by the page's location in the file
system. The following table shows a Razor Page path and the matching URL:
TABLE 1
File name and path matching URL
/Pages/Index.cshtml / or /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
The runtime looks for Razor Pages files in the Pages folder by default.
Index is the default page when a URL doesn't include a page.
For the samples in this document, the DbContext is initialized in the Startup.cs file.
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string Name { get; set; }
}
}
The db context:
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Models;
namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Customer> Customers { get; set; }
}
}
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using RazorPagesContacts.Models;
using System.Threading.Tasks;
namespace RazorPagesContacts.Pages.Customers
{
public class CreateModel : PageModel
{
private readonly CustomerDbContext _context;
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
By convention, the PageModel class is called <PageName>Model and is in the same namespace as
the page.
The PageModel class allows separation of the logic of a page from its presentation. It defines
page handlers for requests sent to the page and the data used to render the page. This
separation allows:
The page has an OnPostAsync handler method, which runs on POST requests (when a user posts
the form). Handler methods for any HTTP verb can be added. The most common handlers are:
OnGet to initialize state needed for the page. In the preceding code, the OnGet method
displays the CreateModel.cshtml Razor Page.
OnPost to handle form submissions.
The Async naming suffix is optional but is often used by convention for asynchronous functions.
The preceding code is typical for Razor Pages.
The OnPostAsync code in the preceding example looks similar to typical controller code.
Most of the MVC primitives like model binding, validation, and action results work the
same with Controllers and Razor Pages.
The previous OnPostAsync method:
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum length of 10."
data-val-length-max="10" data-val-required="The Name field is required."
id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
[BindProperty]
public Customer Customer { get; set; }
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Razor Pages, by default, bind properties only with non-GET verbs. Binding to properties
removes the need to writing code to convert HTTP data to the model type. Binding reduces
code by using the same property to render form fields (<input asp-for="Customer.Name">) and
accept the input.
Warning
For security reasons, you must opt in to binding GET request data to page model properties.
Verify user input before mapping it to properties. Opting into GET binding is useful when
addressing scenarios that rely on query string or route values.
To bind a property on GET requests, set the [BindProperty] attribute's SupportsGet property
to true:
[BindProperty(SupportsGet = true)]
For more information, see ASP.NET Core Community Standup: Bind on GET discussion
(YouTube).
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
In the preceding code, the input tag helper <input asp-for="Customer.Name" /> binds the
HTML <input> element to the Customer.Name model expression.
@addTagHelper makes Tag Helpers available.
@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
if (contact != null)
{
_context.Customers.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
}
The <a /a> Anchor Tag Helper used the asp-route-{value} attribute to generate a link to the
Edit page. The link contains route data with the contact ID. For
example, https://localhost:5001/Edit/1. Tag Helpers enable server-side code to participate in
creating and rendering HTML elements in Razor files.
The Index.cshtml file contains markup to create a delete button for each customer contact:
When the delete button is rendered in HTML, its formaction includes parameters for:
When the button is selected, a form POST request is sent to the server. By convention, the name
of the handler method is selected based on the value of the handler parameter according to
the scheme OnPost[handler]Async.
Because the handler is delete in this example, the OnPostDeleteAsync handler method is used to
process the POST request. If the asp-page-handler is set to a different value, such as remove, a
handler method with the name OnPostRemoveAsync is selected.
if (contact != null)
{
_context.Customers.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div>
<button type="submit">Save</button>
</div>
</form>
The first line contains the @page "{id:int}" directive. The routing constraint"{id:int}" tells the
page to accept requests to the page that contain int route data. If a request to the page
doesn't contain route data that can be converted to an int, the runtime returns an HTTP 404
(not found) error. To make the ID optional, append ? to the route constraint:
@page "{id:int?}"
if (Customer == null)
{
return RedirectToPage("./Index");
}
return Page();
}
_context.Attach(Customer).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw new Exception($"Customer {Customer.Id} not found!");
}
return RedirectToPage("./Index");
}
}
Validation
Validation rules:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string Name { get; set; }
}
}
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer.Name"></span>
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum length of 10."
data-val-length-max="10" data-val-required="The Name field is required."
id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>
<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>
Posting the Create form without a name value displays the error message "The Name field is
required." on the form. If JavaScript is enabled on the client, the browser displays the error
without posting to the server.
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}
The validation attributes specify behavior to enforce on the model properties they're applied
to:
The Required and MinimumLength attributes indicate that a property must have a value, but
nothing prevents a user from entering white space to satisfy this validation.
The RegularExpression attribute is used to limit what characters can be input. In the
preceding code, "Genre":
o Must only use letters.
o The first letter is required to be uppercase. White space, numbers, and special
characters are not allowed.
The RegularExpression "Rating":
o Requires that the first character be an uppercase letter.
o Allows special characters and numbers in subsequent spaces. "PG-13" is valid for a
rating, but fails for a "Genre".
The Range attribute constrains a value to within a specified range.
The StringLength attribute sets the maximum length of a string property, and optionally
its minimum length.
Value types (such as decimal, int, float, DateTime) are inherently required and don't need
the [Required] attribute.
The Create page for the Movie model shows displays errors with invalid values:
For more information, see:
Razor Pages falls back to calling the OnGet handler if no OnHead handler is defined.
<!DOCTYPE html>
<html>
<head>
<title>RP Sample</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<a asp-page="/Index">Home</a>
<a asp-page="/Customers/Create">Create</a>
<a asp-page="/Customers/Index">Customers</a> <br />
@RenderBody()
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>
The Layout:
Controls the layout of each page (unless the page opts out of layout).
Imports HTML structures such as JavaScript and stylesheets.
The contents of the Razor page are rendered where @RenderBody() is called.
@{
Layout = "_Layout";
}
The layout is in the Pages/Shared folder. Pages look for other views (layouts, templates,
partials) hierarchically, starting in the same folder as the current page. A layout in
the Pages/Shared folder can be used from any Razor page under the Pages folder.
We recommend you not put the layout file in the Views/Shared folder. Views/Shared is an MVC
views pattern. Razor Pages are meant to rely on folder hierarchy, not path conventions.
View search from a Razor Page includes the Pages folder. The layouts, templates, and partials
used with MVC controllers and conventional Razor views just work.
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@namespaceis explained later in the tutorial. The @addTagHelper directive brings in the built-in
Tag Helpers to all the pages in the Pages folder.
The @namespace directive set on a page:
@page
@namespace RazorPagesIntro.Pages.Customers
@model NameSpaceModel
<h2>Name space</h2>
<p>
@Model.Message
</p>
The @namespace directive sets the namespace for the page. The @model directive doesn't need to
include the namespace.
For example, the PageModel class Pages/Customers/Edit.cshtml.cs explicitly sets the namespace:
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
The generated namespace for the Pages/Customers/Edit.cshtml Razor Page is the same as
the PageModel class.
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer.Name"></span>
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
The updated Pages/Create.cshtml view file with _ViewImports.cshtml and the preceding layout
file:
@page
@model CreateModel
<form method="post">
Name:
<input asp-for="Customer.Name" />
<input type="submit" />
</form>
In the preceding code, the _ViewImports.cshtml imported the namespace and Tag Helpers. The
layout file imported the JavaScript files.
For more information on partial views, see Partial views in ASP.NET Core.
URL generation for Pages
The Create page, shown previously, uses RedirectToPage:
[BindProperty]
public Customer Customer { get; set; }
_context.Customers.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
/Pages
o Index.cshtml
o Privacy.cshtml
o /Customers
Create.cshtml
Edit.cshtml
Index.cshtml
The Pages/Customers/Create.cshtml and Pages/Customers/Edit.cshtml pages redirect
to Pages/Customers/Index.cshtml after success. The string ./Index is a relative page name used
to access the preceding page. It is used to generate URLs to
the Pages/Customers/Index.cshtml page. For example:
Url.Page("./Index", ...)
<a asp-page="./Index">Customers Index Page</a>
RedirectToPage("./Index")
The absolute page name /Index is used to generate URLs to the Pages/Index.cshtml page. For
example:
Url.Page("/Index", ...)
<a asp-page="/Index">Home Index Page</a>
RedirectToPage("/Index")
The page name is the path to the page from the root /Pages folder including a leading / (for
example, /Index). The preceding URL generation samples offer enhanced options and
functional capabilities over hard-coding a URL. URL generation uses routing and can generate
and encode parameters according to how the route is defined in the destination path.
URL generation for pages supports relative names. The following table shows which Index page
is selected using different RedirectToPage parameters in Pages/Customers/Create.cshtml.
TABLE 2
RedirectToPage(x) Page
RedirectToPage("/Index") Pages/Index
RedirectToPage("./Index"); Pages/Customers/Index
RedirectToPage("../Index") Pages/Index
RedirectToPage("Index") Pages/Customers/Index
RedirectToPage("Index"), RedirectToPage("./Index") ,
and RedirectToPage("../Index") are relative
names. The RedirectToPage parameter is combined with the path of the current page to
compute the name of the destination page.
Relative name linking is useful when building sites with a complex structure. When relative
names are used to link between pages in a folder:
ViewData attribute
Data can be passed to a page with ViewDataAttribute. Properties with the [ViewData] attribute
have their values stored and loaded from the ViewDataDictionary.
In the following example, the AboutModel applies the [ViewData] attribute to the Title property:
<h1>@Model.Title</h1>
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
TempData
ASP.NET Core exposes the TempData. This property stores data until it's read.
The Keep and Peek methods can be used to examine the data without deletion. TempData is
useful for redirection, when data is needed for more than a single request.
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
<h3>Msg: @Model.Message</h3>
[TempData]
public string Message { get; set; }
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>
The form in the preceding example has two submit buttons, each using
the FormActionTagHelper to submit to a different URL. The asp-page-handler attribute is a
companion to asp-page. asp-page-handler generates URLs that submit to each of the handler
methods defined by a page. asp-page isn't specified because the sample is linking to the
current page.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
namespace RazorPagesContacts.Pages.Customers
{
public class CreateFATHModel : PageModel
{
private readonly AppDbContext _db;
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
The preceding code uses named handler methods. Named handler methods are created by
taking the text in the name after On<HTTP Verb> and before Async (if present). In the preceding
example, the page methods are OnPostJoinListAsync and OnPostJoinListUCAsync.
With OnPost and Async removed, the handler names are JoinList and JoinListUC.
Specify a custom route to a page. For example, the route to the About page can be set
to /Some/Other/Path with @page "/Some/Other/Path".
Append segments to a page's default route. For example, an "item" segment can be
added to a page's default route with @page "item".
Append parameters to a page's default route. For example, an ID parameter, id, can be
required for a page with @page "{id}".
A root-relative path designated by a tilde (~) at the beginning of the path is supported. For
example, @page "~/Some/Other/Path" is the same as @page "/Some/Other/Path".
If you don't like the query string ?handler=JoinList in the URL, change the route to put the
handler name in the path portion of the URL. The route can be customized by adding a route
template enclosed in double quotes after the @page directive.
@page "{handler?}"
@model CreateRouteModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>
Use the RazorPagesOptions to set the root directory for pages, or add application model
conventions for pages. For more information on conventions, see Razor Pages authorization
conventions.
Add WithRazorPagesRoot to specify that Razor Pages are at a custom root directory in the app
(provide a relative path):