Introduction à Razor Pages dans ASP.NET Core

Par Rick Anderson, Dave Brock et Kirk Larkin

Razor Pages peut rendre le codage des scénarios orientés page plus faciles et plus productifs qu’en utilisant des contrôleurs et des vues.

Si vous cherchez un didacticiel qui utilise l’approche Model-View-Controller, consultez Bien démarrer avec ASP.NET Core MVC.

Ce document fournit une introduction à Razor Pages. Il ne s’agit pas d’un didacticiel pas à pas. Si certaines sections vous semblent trop avancées, consultez Bien démarrer avec Razor Pages. Pour une vue d’ensemble d’ASP.NET Core, consultez Introduction à ASP.NET Core.

Prérequis

Créer un projet Razor Pages

Pour obtenir des instructions sur la création d’un projet Razor Pages, consultez Bien démarrer avec Razor Pages.

Razor Pages

Razor Pages est activé dans Program.cs :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Dans le code précédent :

Considérez une page de base :

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

Le code précédent ressemble beaucoup à un fichier de vue Razor utilisé dans une application ASP.NET Core avec des contrôleurs et des vues. Ce qui le rend différent est la directive @page. @page fait du fichier une action MVC, ce qui signifie qu’il gère les demandes directement, sans passer par un contrôleur. @page doit être la première directive Razor sur une page. @page affecte le comportement d’autres constructions Razor. Les noms de fichier Razor Pages ont un suffixe .cshtml.

Une page similaire, utilisant une classe PageModel, est illustrée dans les deux fichiers suivants. Le fichier Pages/Index2.cshtml :

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Le modèle de page Pages/Index2.cshtml.cs :

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#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

Par convention, le fichier de classe PageModel a le même nom que le fichier de page Razor, .cs y étant ajouté. Par exemple, la page Razor précédente est Pages/Index2.cshtml. Le fichier contenant la classe PageModel est nommé Pages/Index2.cshtml.cs.

Les associations des chemins d’URL aux pages sont déterminées par l’emplacement de la page dans le système de fichiers. Le tableau suivant montre un chemin de page Razor et l’URL correspondante :

Nom et chemin de fichier URL correspondante
/Pages/Index.cshtml / ou /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store ou /Store/Index

Remarques :

  • Le runtime recherche les fichiers Razor Pages dans le dossier Pages par défaut.
  • Index est la page par défaut quand une URL n’inclut pas de page.

Écrire un formulaire de base

Razor Pages est conçu pour que les modèles courants utilisés avec les navigateurs web soient faciles à implémenter lors de la création d’une application. La liaison de modèle, les Tag Helpers et les assistances HTML fonctionnent avec les propriétés définies dans une classe de page Razor. Considérez une page qui implémente un formulaire « Nous contacter » de base pour le modèle Contact :

Pour les exemples de ce document, le DbContext est initialisé dans le fichier Startup.cs.

La base de données en mémoire nécessite le package NuGet Microsoft.EntityFrameworkCore.InMemory.

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Le modèle de données :

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string? Name { get; set; }
    }
}

Le contexte de la base de données :

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext (DbContextOptions<CustomerDbContext> options)
            : base(options)
        {
        }

        public DbSet<RazorPagesContacts.Models.Customer> Customer => Set<RazorPagesContacts.Models.Customer>();
    }
}

Le fichier de vue Pages/Customers/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

Le modèle de page Pages/Customers/Create.cshtml.cs :

public class CreateModel : PageModel
{
    private readonly Data.CustomerDbContext _context;

    public CreateModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

Par convention, la classe PageModel se nomme <PageName>Model et se trouve dans le même espace de noms que la page.

La classe PageModel permet de séparer la logique d’une page de sa présentation. Elle définit des gestionnaires de page pour les demandes envoyées à la page et les données utilisées pour l’afficher. Cette séparation permet :

La page a une méthode de gestionnaireOnPostAsync, qui s’exécute sur les requêtes POST (quand un utilisateur poste le formulaire). Vous pouvez ajouter des méthodes de gestionnaire pour n’importe quel verbe HTTP. Les gestionnaires les plus courants sont :

  • OnGet pour initialiser l’état nécessaire pour la page. Dans le code précédent, la méthode OnGet affiche la page RazorCreateModel.cshtml.
  • OnPost pour gérer les envois de formulaire.

Le suffixe de nommage Async est facultatif, mais souvent utilisé par convention pour les fonctions asynchrones. Le code précédent est typique de Razor Pages.

Si vous êtes familiarisé avec les applications ASP.NET utilisant des contrôleurs et des vues :

  • Le code de OnPostAsync dans l’exemple précédent est similaire à du code généralement utilisé dans un contrôleur.
  • La plupart des primitives MVC comme la liaison de modèle, la validation et les résultats des actions fonctionnent de la même façon avec les contrôleurs et Razor Pages.

La méthode OnPostAsync précédente :

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    if (Customer != null) _context.Customer.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Le flux de base de OnPostAsync :

Vérifiez s’il y a des erreurs de validation.

  • S’il n’y a aucune erreur, enregistrez les données et redirigez.
  • S’il y a des erreurs, réaffichez la page avec des messages de validation. Dans de nombreux cas, les erreurs de validation seraient détectées sur le client et jamais envoyées au serveur.

Le fichier de vue Pages/Customers/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>

Le HTML rendu à partir de Pages/Customers/Create.cshtml :

<p>Enter a customer name:</p>

<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>

Dans le code précédent, le formulaire est publié :

  • Avec des données valides :

    • La méthode de gestionnaire OnPostAsync appelle la méthode d’assistance RedirectToPage. RedirectToPage retourne une instance de RedirectToPageResult. RedirectToPage:

      • Est un résultat de l’action.
      • Est similaire à RedirectToAction ou à RedirectToRoute (utilisé dans les contrôleurs et les vues).
      • Est personnalisé pour les pages. Dans l’exemple précédent, il redirige vers la page Index racine (/Index). RedirectToPage est détaillé dans la section Génération d’URL pour les pages.
  • Avec les erreurs de validation passées au serveur :

    • La méthode de gestionnaire OnPostAsync appelle la méthode d’assistance Page. Page retourne une instance de PageResult. Le retour de Page est similaire à la façon dont les actions dans les contrôleurs retournent View. PageResult est le type de retour par défaut pour une méthode de gestionnaire. Une méthode de gestionnaire qui retourne void restitue la page.
    • Dans l’exemple précédent, la publication du formulaire sans valeur fait que ModelState.IsValid retourne false. Dans cet exemple, aucune erreur de validation n’est affichée sur le client. La gestion des erreurs de validation est traitée plus loin dans ce document.
    [BindProperty]
    public Customer? Customer { get; set; }
    
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • Avec les erreurs de validation détectées par la validation côté client :

    • Les données ne sont pas publiées sur le serveur.
    • La validation côté client est expliquée plus loin dans ce document.

La propriété Customer utilise l’attribut [BindProperty] pour accepter la liaison de modèle :

public class CreateModel : PageModel
{
    private readonly Data.CustomerDbContext _context;

    public CreateModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

[BindProperty] ne doit pas être utilisé sur les modèles contenant des propriétés qui ne doivent pas être modifiées par le client. Pour plus d’informations, consultez Surpublication.

Par défaut, Razor Pages lie les propriétés seulement avec des verbes non-GET. La liaison à des propriétés supprime la nécessité d’écrire du code pour convertir des données HTTP dans le type du modèle. Elle réduit la quantité de code en utilisant la même propriété pour afficher les champs de formulaire (<input asp-for="Customer.Name">) et accepter l’entrée.

Avertissement

Pour des raisons de sécurité, vous devez choisir de lier les données de requête GET aux propriétés du modèle de page. Vérifiez l’entrée utilisateur avant de la mapper à des propriétés. Le choix de la liaison GET convient pour les scénarios qui s’appuient sur des valeurs de routage ou de chaîne de requête.

Pour lier une propriété sur des requêtes GET, définissez la propriété SupportsGet de l’attribut [BindProperty] sur true :

[BindProperty(SupportsGet = true)]

Pour plus d’informations, consultez ASP.NET Core Community Standup: Bind on GET discussion (YouTube).

Examen du fichier de vue Pages/Customers/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer!.Name" />
    <input type="submit" />
</form>
  • Dans le code précédent, le Tag Helper d’entrée<input asp-for="Customer.Name" /> lie l’élément HTML <input> à l’expression de modèle Customer.Name.
  • @addTagHelper rend les Tag Helpers disponibles.

La page d’accueil

Index.cshtml est la page d’accueil :

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
        @if (Model.Customers != null)
        {
            foreach (var contact in Model.Customers)
            {
                <tr>
                    <td> @contact.Id </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

La classe PageModel associée (Index.cshtml.cs) :

public class IndexModel : PageModel
{
    private readonly Data.CustomerDbContext _context;
    public IndexModel(Data.CustomerDbContext context)
    {
        _context = context;
    }

    public IList<Customer>? Customers { get; set; }

    public async Task OnGetAsync()
    {
        Customers = await _context.Customer.ToListAsync();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customer.FindAsync(id);

        if (contact != null)
        {
            _context.Customer.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

Le fichier Index.cshtml contient le balisage suivant :

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

Le <a /a>Tag Helper d’ancre a utilisé l’attribut asp-route-{value} pour générer un lien vers la page Edit. Le lien contient des données d’itinéraire avec l’ID de contact. Par exemple : https://localhost:5001/Edit/1. Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu des éléments HTML dans les fichiers Razor.

Le fichier Index.cshtml contient également le balisage pour créer un bouton Delete (Supprimer) pour chaque contact client :

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

Le HTML rendu :

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

Quand le bouton Delete est rendu en HTML, son action de formulaire inclut des paramètres pour :

  • L’ID du contact client spécifié par l’attribut asp-route-id.
  • Le handler spécifié par l’attribut asp-page-handler.

Quand le bouton est sélectionné, une demande POST de forumaire est envoyée au serveur. Par convention, le nom de la méthode de gestionnaire est sélectionné en fonction de la valeur du paramètre handler conformément au schéma OnPost[handler]Async.

Étant donné que le handler est delete dans cet exemple, la méthode de gestionnaire OnPostDeleteAsync est utilisée pour traiter la demande POST. Si asp-page-handler est défini sur une autre valeur, comme remove, une méthode de gestionnaire avec le nom OnPostRemoveAsync est sélectionnée.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customer.FindAsync(id);

    if (contact != null)
    {
        _context.Customer.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

La méthode OnPostDeleteAsync :

  • Obtient l’id de la chaîne de requête.
  • Interroge la base de données pour le contact client avec FindAsync.
  • Si le contact client est trouvé, il est supprimé et la base de données est mise à jour.
  • Appelle RedirectToPage pour rediriger vers la page Index racine (/Index).

Le fichier Edit.cshtml

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Customer</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Customer!.Id" />
            <div class="form-group">
                <label asp-for="Customer!.Name" class="control-label"></label>
                <input asp-for="Customer!.Name" class="form-control" />
                <span asp-validation-for="Customer!.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

La première ligne contient la directive @page "{id:int}". La contrainte de routage "{id:int}" indique à la page qu’elle doit accepter les requêtes qui contiennent des données de routage int. Si une requête à la page ne contient de données d’itinéraire qui peuvent être converties en int, le runtime retourne une erreur HTTP 404 (introuvable). Pour que l’ID soit facultatif, ajoutez ? à la contrainte d’itinéraire :

@page "{id:int?}"

Le fichier Edit.cshtml.cs :

public class EditModel : PageModel
{
    private readonly RazorPagesContacts.Data.CustomerDbContext _context;

    public EditModel(RazorPagesContacts.Data.CustomerDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Customer? Customer { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id == id);
        
        if (Customer == null)
        {
            return NotFound();
        }
        return Page();
    }

    // To protect from overposting attacks, enable the specific properties you want to bind to.
    // For more details, see https://aka.ms/RazorPagesCRUD.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        if (Customer != null)
        {
            _context.Attach(Customer).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!CustomerExists(Customer.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
        }

        return RedirectToPage("./Index");
    }

    private bool CustomerExists(int id)
    {
        return _context.Customer.Any(e => e.Id == id);
    }
}

Validation

Les règles de validation :

  • Sont spécifiées de manière déclarative dans la classe de modèle.
  • Sont appliquées partout dans l’application.

L’espace de noms System.ComponentModel.DataAnnotations fournit un ensemble d’attributs de validation intégrés qui sont appliqués de façon déclarative à une classe ou à une propriété. DataAnnotations contient également des attributs de mise en forme, comme [DataType], qui aident à effectuer la mise en forme et ne fournissent aucune validation.

Considérons le modèle Customer :

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string? Name { get; set; }
    }
}

En utilisant le fichier de vue suivant Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<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>

Le code précédent :

  • Inclut jQuery et des scripts de validation jQuery.

  • Utilise le <div /> et <span />Tag Helpers pour permettre :

    • La validation côté client.
    • Le rendu des erreurs de validation.
  • Génère le code HTML suivant :

    <p>Enter a customer name:</p>
    
    <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>
    

La publication du formulaire Create (Créer) sans valeur pour le nom affiche le message d’erreur « The Name field is required. » (Le champ Nom est requis.) sur le formulaire. Si JavaScript est activé sur le client, le navigateur affiche l’erreur sans publier sur le serveur.

L’attribut [StringLength(10)] génère data-val-length-max="10" sur le HTML rendu. data-val-length-max empêche les navigateurs d’autoriser une entrée supérieure à la longueur maximale spécifiée. Si un outil comme Fiddler est utilisé pour modifier et refaire la publication :

  • Avec le nom d’une longueur supérieure à 10.
  • Le message d’erreur « The field Name must be a string with a maximum length of 10. » (Le nom du champ doit être une chaîne dont la longueur maximale est de 10.) est retourné.

Considérez le modèle Movie suivant :

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [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; }
    }
}

Les attributs de validation spécifient le comportement à appliquer sur les propriétés du modèle auxquelles ils sont appliqués :

  • Les attributs Required et MinimumLength indiquent qu’une propriété doit avoir une valeur, mais rien n’empêche un utilisateur d’entrer un espace pour satisfaire à cette validation.

  • L’attribut RegularExpression sert à limiter les caractères pouvant être entrés. Dans le code précédent, « Genre » :

    • Doit utiliser seulement des lettres.
    • La première lettre doit être une majuscule. Les espaces, les chiffres et les caractères spéciaux ne sont pas autorisés.
  • L’expression RegularExpression « Rating » :

    • Nécessite que le premier caractère soit une lettre majuscule.
    • Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent. « PG-13 » est valide pour une évaluation, mais échoue pour un « Genre ».
  • L’attribut Range limite une valeur à une plage spécifiée.

  • L’attribut StringLength définit la longueur maximale d’une propriété de chaîne, et éventuellement sa longueur minimale.

  • Les types valeur (tels que decimal, int, float et DateTime) sont obligatoires par nature et n’ont pas besoin de l’attribut [Required].

La page Create pour le Movie modèle montre les erreurs avec des valeurs non valides :

Formulaire de vue Movie avec plusieurs erreurs de validation jQuery côté client

Pour plus d'informations, consultez les pages suivantes :

Isolation CSS

Isolez les styles CSS en pages, vues et composants individuels afin de réduire ou d’éviter :

  • Les dépendances sur les styles globaux qui peuvent être difficiles à gérer.
  • Les conflits de style dans du contenu imbriqué.

Pour ajouter un fichier CSS délimité pour une page ou une vue, placez les styles CSS dans un fichier .cshtml.css compagnon correspondant au nom du fichier .cshtml. Dans l’exemple suivant, un fichier Index.cshtml.cssfournit des styles CSS qui sont appliqués seulement à la page ou à la vue Index.cshtml.

Pages/Index.cshtml.css (Razor Pages) ou Views/Index.cshtml.css (MVC) :

h1 {
    color: red;
}

L’isolation CSS se produit au moment de la build. Le framework réécrit les sélecteurs CSS pour qu’ils correspondent au balisage rendu par les pages ou les vues de l’application. Les styles CSS réécrits sont regroupés et produits sous la forme d’une ressource statique. {APP ASSEMBLY}.styles.css L’espace réservé {APP ASSEMBLY} est le nom de l’assembly du projet. Un lien vers les styles CSS regroupés est placé dans la disposition de l’application.

Dans le contenu <head> du fichier Pages/Shared/_Layout.cshtml de l’application (Razor Pages) ou de Views/Shared/_Layout.cshtml (MVC), ajoutez ou vérifiez la présence du lien vers les styles CSS regroupés :

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

Dans l’exemple suivant, le nom de l’assembly de l’application est WebApp :

<link rel="stylesheet" href="WebApp.styles.css" />

Les styles définis dans un fichier CSS délimité sont appliqués seulement à la sortie rendue du fichier correspondant. Dans l’exemple précédent, les déclarations CSS h1 définies ailleurs dans l’application ne sont pas en conflit avec le style de titre de Index. Les règles d’héritage et de cascade des styles CSS restent en vigueur pour les fichiers CSS délimités. Par exemple, les styles appliqués directement à un élément <h1> du fichier Index.cshtml remplacent les styles du fichier CSS délimité dans Index.cshtml.css.

Notes

Pour garantir l’isolation du style CSS lors du regroupement, l’importation de CSS dans des blocs de code Razor n’est pas prise en charge.

L’isolation CSS s’applique seulement aux éléments HTML. L’isolation CSS n’est pas prise en charge pour les Tag Helpers.

Dans le fichier CSS regroupé, chaque page, vue ou composant Razor est associé à un identificateur d’étendue au format b-{STRING}, où l’espace réservé {STRING} est une chaîne de dix caractères générée par le framework. L’exemple suivant fournit le style pour l’élément <h1> précédent dans la page Index d’une application Razor Pages :

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
    color: red;
}

Dans la page Index où le style CSS est appliqué à partir du fichier regroupé, l’identificateur d’étendue est ajouté en tant qu’attribut HTML :

<h1 b-3xxtam6d07>

L’identificateur est unique pour une application. Au moment de la build, un bundle de projet est créé avec la convention {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css, où l’espace réservé {STATIC WEB ASSETS BASE PATH} est le chemin de base des ressources web statiques.

Si d’autres projets sont utilisés, comme des packages NuGet ou des bibliothèques de classes Razor, le fichier regroupé :

  • Fait référence aux styles en utilisant des importations CSS.
  • N’est pas publié en tant que ressource web statique de l’application qui consomme les styles.

Prise en charge des préprocesseurs CSS

Les préprocesseurs CSS sont utiles pour améliorer le développement CSS en utilisant des fonctionnalités comme les variables, l’imbrication, les modules, les mixins et l’héritage. Bien que l’isolation CSS ne prenne pas en charge nativement les préprocesseurs CSS comme Sass ou Less, l’intégration de préprocesseurs CSS se fait sans problème dès lors que la compilation du préprocesseur se produit avant que le framework réécrive les sélecteurs CSS lors du processus de build. Par exemple, avec Visual Studio, configurez la compilation du préprocesseur existant en tant que tâche Avant la build dans l’Explorateur d’exécuteur de tâches Visual Studio.

De nombreux packages NuGet tiers, comme Delegate.SassBuilder, peuvent compiler des fichiers SASS/SCSS au début du processus de build avant que l’isolation CSS ne se produise, et aucune configuration supplémentaire n’est requise.

Configuration de l’isolation CSS

L’isolation CSS permet la configuration de certains scénarios avancés, comme quand il existe des dépendances sur des outils ou des workflows existants.

Personnaliser le format de l’identificateur d’étendue

Dans cette section, l’espace réservé {Pages|Views} est Pages pour les applications Razor Pages ou Views pour les applications MVC.

Par défaut, les identificateurs d’étendue utilisent le format b-{STRING}, où l’espace réservé {STRING} est une chaîne de dix caractères générée par le framework. Pour personnaliser le format de l’identificateur d’étendue, mettez à jour le fichier projet avec un modèle souhaité :

<ItemGroup>
  <None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Dans l’exemple précédent, le CSS généré pour Index.cshtml.css change son identificateur d’étendue de b-{STRING} en custom-scope-identifier.

Utilisez des identificateurs d’étendue pour mettre en œuvre l’héritage avec des fichiers CSS délimités. Dans l’exemple de fichier projet suivant, un fichier BaseView.cshtml.css contient des styles communs entre les vues. Un fichier DerivedView.cshtml.css hérite de ces styles.

<ItemGroup>
  <None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-identifier" />
  <None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Utilisez l’opérateur de caractère générique (*) pour partager des identificateurs d’étendue entre plusieurs fichiers :

<ItemGroup>
  <None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>

Changer le chemin de base pour les ressources web statiques

Le fichier CSS délimité est généré à la racine de l’application. Dans le fichier projet, utilisez la propriété StaticWebAssetBasePath pour changer le chemin par défaut. L’exemple suivant place le fichier CSS délimité et le reste des ressources de l’application dans le chemin _content :

<PropertyGroup>
  <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Désactiver le regroupement automatique

Pour ne pas accepter la façon dont l’infrastructure publie et charge des fichiers délimités au moment de l’exécution, utilisez la propriété DisableScopedCssBundling. Lors de l’utilisation de cette propriété, d’autres outils ou processus sont chargés de prendre les fichiers CSS isolés du répertoire obj, et de les publier et de les charger au moment de l’exécution :

<PropertyGroup>
  <DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Prise en charge de la bibliothèque de classes Razor (RCL)

Quand une bibliothèque de classes Razor(RCL) fournit des styles isolés, l’attribut href de la balise <link> pointe vers {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css, où les espaces réservés sont :

  • {STATIC WEB ASSET BASE PATH} : le chemin de base de la ressource web statique.
  • {PACKAGE ID} : l’identificateur de package de la bibliothèque. L’identificateur de package est défini par défaut sur le nom de l’assembly du projet si l’identificateur de package n’est pas spécifié dans le fichier projet.

Dans l’exemple suivant :

  • Le chemin de base de la ressource web statique est _content/ClassLib.
  • Le nom de l’assembly de la bibliothèque de classes est ClassLib.

Pages/Shared/_Layout.cshtml (Razor Pages) ou Views/Shared/_Layout.cshtml (MVC) :

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

Pour plus d’informations sur les bibliothèques de classes Razor, consultez les articles suivants :

Pour plus d’informations sur l’isolation CSS de Blazor, consultez Isolation CSS Blazor d’ASP.NET Core.

Gérer les requêtes HEAD avec un gestionnaire OnGet de secours

Les demandes HEAD vous permettent de récupérer les en-têtes pour une ressource spécifique. Contrairement aux requêtes GET, les requêtes HEAD ne retournent pas un corps de réponse.

En règle générale, un gestionnaire OnHead est créé et appelé pour les requêtes HEAD :

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

Razor Pages se rabat sur un appel du gestionnaire OnGet si aucun gestionnaire OnHead n’est défini.

XSRF/CSRF et Razor Pages

Les pages Razor sont protégées par la validation antifalsification. FormTagHelper injecte des jetons antifalsification dans les éléments de formulaire HTML.

Utilisation de dispositions, de lignes de code partiellement exécutées, de modèles et de Tag Helpers avec Razor Pages

Les pages fonctionnent avec toutes les fonctionnalités du moteur de vue Razor. Les dispositions, les lignes de code partiellement exécutées, les modèles, les Tag Helpers, _ViewStart.cshtml et _ViewImports.cshtml fonctionnent de la même façon que pour les vues Razor conventionnelles.

Nous allons nettoyer un peu cette page en tirant parti de certaines de ces fonctionnalités.

Ajoutez une page de disposition à Pages/Shared/_Layout.cshtml :

<!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>

La disposition :

  • Contrôle la disposition de chaque page (à moins que la page ne refuse la disposition).
  • Importe des structures HTML telles que JavaScript et les feuilles de style.
  • Le contenu de la page Razor est rendu là où @RenderBody() est appelé.

Pour plus d’informations, consultez Page de disposition.

La propriété Layout est définie dans Pages/_ViewStart.cshtml :

@{
    Layout = "_Layout";
}

La disposition est dans le dossier Pages/Shared. Les pages recherchent d’autres vues (dispositions, modèles, partiels) hiérarchiquement, en commençant dans le même dossier que la page active. Une disposition dans le dossier Pages/Shared peut être utilisée depuis n’importe quelle page Razor sous le dossier Pages.

Le fichier de disposition doit être placé dans le dossier Pages/Shared.

Nous vous recommandons de ne pas placer le fichier de disposition dans le dossier Views/Shared. Views/Shared est un modèle de vues MVC. Les pages Razor sont censées s’appuyer sur la hiérarchie des dossiers, et non pas sur les conventions des chemins.

La recherche des vues depuis une page Razor inclut le dossier Pages. Les dispositions, les modèles et les lignes de code partiellement exécutées que vous utilisez avec les contrôleurs MVC et les vues Razor conventionnelles fonctionnent normalement.

Ajoutez un fichier Pages/_ViewImports.cshtml :

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace est expliqué plus loin dans le didacticiel. La directive @addTagHelper permet de bénéficier des Tag Helpers intégrés dans toutes les pages du dossier Pages.

La directive @namespace définie sur une page :

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

La directive @namespace définit l’espace de noms pour la page. La directive @model n’a pas besoin d’inclure l’espace de noms.

Quand la directive @namespace est contenue dans _ViewImports.cshtml, l’espace de noms spécifié fournit le préfixe de l’espace de noms généré dans la Page qui importe la directive @namespace. Le reste de l’espace de noms généré (la partie suffixe) est le chemin relatif avec le point comme séparateur entre le dossier contenant _ViewImports.cshtml et le dossier contenant la page.

Par exemple, la classe PageModel de Pages/Customers/Edit.cshtml.cs définit explicitement l’espace de noms :

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

Le fichier Pages/_ViewImports.cshtml définit l’espace de noms suivant :

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

L’espace de noms généré pour la page RazorPages/Customers/Edit.cshtml est identique à la classe PageModel.

@namespace fonctionne également avec les vues Razor conventionnelles.

Considérez le fichier de vue Pages/Customers/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<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>

Le fichier de vue Pages/Customers/Create.cshtml mis à jour avec _ViewImports.cshtml et le fichier de disposition précédent :

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Dans le code précédent, _ViewImports.cshtml a importé l’espace de noms et des Tag Helpers. Le fichier de disposition a importé les fichiers JavaScript.

Le projet de démarrage Razor Pages contient le Pages/_ValidationScriptsPartial.cshtml, qui connecte la validation côté client.

Pour plus d’informations sur les vues partielles, consultez Vues partielles dans ASP.NET Core.

Génération d’URL pour les pages

La page Create, illustrée précédemment, utilise RedirectToPage :

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

L’application a la structure de fichiers/dossiers suivante :

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

Les pages Pages/Customers/Create.cshtml et Pages/Customers/Edit.cshtml redirigent vers Pages/Customers/Index.cshtml après réussite. La chaîne ./Index est un nom de page relatif utilisé pour accéder à la page précédente. Il est utilisé pour générer des URL vers la page Pages/Customers/Index.cshtml. Par exemple :

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

Le nom de page absolu /Index est utilisé pour générer des URL vers la page Pages/Index.cshtml. Par exemple :

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

Le nom de la page est le chemin de la page à partir du dossier racine /Pages avec un / devant (par exemple, /Index). Les exemples de génération d’URL précédents offrent des options améliorées et des capacités fonctionnelles par rapport au codage en dur d’une URL. La génération d’URL utilise le routage et peut générer et encoder des paramètres en fonction de la façon dont l’itinéraire est défini dans le chemin de destination.

La génération d’URL pour les pages prend en charge les noms relatifs. Le tableau suivant montre la page Index sélectionnée en utilisant différents paramètres RedirectToPage dans Pages/Customers/Create.cshtml.

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") et RedirectToPage("../Index") sont des noms relatifs. Le paramètre RedirectToPage est combiné avec le chemin de la page active pour calculer le nom de la page de destination.

La liaison de nom relatif est utile lors de la création de sites avec une structure complexe. Quand des noms relatifs sont utilisés pour lier des pages dans un dossier :

  • Le renommage d’un dossier ne rompt pas les liens relatifs.
  • Les liens ne sont pas rompus, car ils n’incluent pas le nom du dossier.

Pour rediriger vers une page située dans une autre Zone, spécifiez la zone :

RedirectToPage("/Index", new { area = "Services" });

Pour plus d’informations, consultez Zones dans ASP.NET Core et Conventions des routes et des applications Razor dans ASP.NET Core.

Attribut ViewData

Des données peuvent être passées à une page avec ViewDataAttribute. Les valeurs des propriétés ayant l’attribut [ViewData] sont stockées dans le ViewDataDictionary et chargées depuis celui-ci.

Dans l’exemple suivant, AboutModel applique l’attribut [ViewData] à la propriété Title :

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

Dans la page À propos de, accédez à la propriété Title en tant que propriété de modèle :

<h1>@Model.Title</h1>

Dans la disposition, le titre est lu à partir du dictionnaire ViewData :

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempData

ASP.NET Core expose le TempData. Cette propriété stocke les données jusqu’à ce qu’elles soient lues. Vous pouvez utiliser les méthodes Keep et Peek pour examiner les données sans suppression. TempData est utile pour la redirection, quand des données sont nécessaires pour plusieurs requêtes.

Le code suivant définit la valeur de Message à l’aide de TempData :

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

Le balisage suivant dans le fichier Pages/Customers/Index.cshtml affiche la valeur de Message en utilisant TempData.

<h3>Msg: @Model.Message</h3>

Le modèle de page Pages/Customers/Index.cshtml.cs applique l’attribut [TempData] à la propriété Message.

[TempData]
public string Message { get; set; }

Pour plus d’informations, consultez TempData.

Plusieurs gestionnaires par page

La page suivante génère un balisage pour deux gestionnaires en utilisant le Tag Helper asp-page-handler :

@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>
        <!-- <snippet_Handlers> -->
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
        <!-- </snippet_Handlers> -->
    </form>
</body>
</html>

Le formulaire dans l’exemple précédent contient deux boutons d’envoi, chacun utilisant FormActionTagHelper pour envoyer à une URL différente. L’attribut asp-page-handler est un complément de asp-page. asp-page-handler génère des URL qui envoient à chacune des méthodes de gestionnaire définies par une page. asp-page n’est pas spécifié, car l’exemple établit une liaison à la page active.

Le modèle de 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;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostJoinListAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

Le code précédent utilise des méthodes de gestionnaire nommées. Pour créer des méthodes de gestionnaire nommées, il faut prendre le texte dans le nom après On<HTTP Verb> et avant Async (le cas échéant). Dans l’exemple précédent, les méthodes de page sont OnPostJoinListAsync et OnPostJoinListUCAsync. Avec OnPost et Async supprimés, les noms des gestionnaires sont JoinList et JoinListUC.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

Avec le code précédent, le chemin d’URL qui envoie à OnPostJoinListAsync est https://localhost:5001/Customers/CreateFATH?handler=JoinList. Le chemin d’URL qui envoie à OnPostJoinListUCAsync est https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Itinéraires personnalisés

Utilisez la directive @page pour :

  • Spécifier une route personnalisée vers une page. Par exemple, la route vers la page À propos peut être définie sur /Some/Other/Path avec @page "/Some/Other/Path".
  • Ajouter des segments à la route par défaut d’une page. Par exemple, un segment « item » peut être ajouté à la route par défaut d’une page avec @page "item".
  • Ajouter des paramètres à la route par défaut d’une page. Par exemple, un paramètre d’ID, id, peut être nécessaire pour une page avec @page "{id}".

Un chemin relatif racine désigné par un tilde (~) au début du chemin est pris en charge. Par exemple, @page "~/Some/Other/Path" est identique à @page "/Some/Other/Path".

Si vous ne voulez pas avoir la chaîne de requête ?handler=JoinList dans l’URL, changez la route pour placer le nom du gestionnaire dans la partie « chemin » de l’URL. La route peut être personnalisée en ajoutant un modèle de route placé entre des guillemets doubles après la directive @page.

@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>

Avec le code précédent, le chemin d’URL qui envoie à OnPostJoinListAsync est https://localhost:5001/Customers/CreateFATH/JoinList. Le chemin d’URL qui envoie à OnPostJoinListUCAsync est https://localhost:5001/Customers/CreateFATH/JoinListUC.

Le ? suivant handler signifie que le paramètre d’itinéraire est facultatif.

Configuration et paramètres avancés

La configuration et les paramètres des sections suivantes ne sont pas nécessaires pour la plupart des applications.

Pour configurer les options avancées, utilisez la surcharge AddRazorPages qui configure RazorPagesOptions :

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.RootDirectory = "/MyPages";
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
});

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Utilisez RazorPagesOptions pour définir le répertoire racine pour les pages ou ajouter des conventions de modèle d’application pour les pages. Pour plus d’informations sur les conventions, consultez Conventions des autorisations de Razor Pages.

Pour précompiler des vues, consultez Compilation des vues Razor.

Spécifier que les pages Razor se trouvent à la racine du contenu

Par défaut, les pages Razor sont placées à la racine du répertoire /Pages. Ajoutez WithRazorPagesAtContentRoot pour spécifier que vos pages Razor se trouvent à la racine du contenu (ContentRootPath) de l’application :

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
  .WithRazorPagesAtContentRoot();

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Spécifier que les pages Razor se trouvent dans un répertoire racine personnalisé

Ajoutez WithRazorPagesRoot pour spécifier que vos pages Razor se trouvent dans un répertoire racine personnalisé de l’application (fournissez un chemin relatif) :

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
  .WithRazorPagesRoot("/path/to/razor/pages");

builder.Services.AddDbContext<CustomerDbContext>(options =>
    options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Ressources supplémentaires

Créer un projet Razor Pages

Pour obtenir des instructions sur la création d’un projet Razor Pages, consultez Bien démarrer avec Razor Pages.

Razor Pages

Razor Pages est activé dans Startup.cs :

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

Considérez une page de base :

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

Le code précédent ressemble beaucoup à un fichier de vue Razor utilisé dans une application ASP.NET Core avec des contrôleurs et des vues. Ce qui le rend différent est la directive @page. @page fait du fichier une action MVC, ce qui signifie qu’il gère les demandes directement, sans passer par un contrôleur. @page doit être la première directive Razor sur une page. @page affecte le comportement d’autres constructions Razor. Les noms de fichier Razor Pages ont un suffixe .cshtml.

Une page similaire, utilisant une classe PageModel, est illustrée dans les deux fichiers suivants. Le fichier Pages/Index2.cshtml :

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Le modèle de page Pages/Index2.cshtml.cs :

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#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

Par convention, le fichier de classe PageModel a le même nom que le fichier de page Razor, .cs y étant ajouté. Par exemple, la page Razor précédente est Pages/Index2.cshtml. Le fichier contenant la classe PageModel est nommé Pages/Index2.cshtml.cs.

Les associations des chemins d’URL aux pages sont déterminées par l’emplacement de la page dans le système de fichiers. Le tableau suivant montre un chemin de page Razor et l’URL correspondante :

Nom et chemin de fichier URL correspondante
/Pages/Index.cshtml / ou /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml /Store ou /Store/Index

Remarques :

  • Le runtime recherche les fichiers Razor Pages dans le dossier Pages par défaut.
  • Index est la page par défaut quand une URL n’inclut pas de page.

Écrire un formulaire de base

Razor Pages est conçu pour que les modèles courants utilisés avec les navigateurs web soient faciles à implémenter lors de la création d’une application. La liaison de modèle, les Tag Helpers et les assistances HTML fonctionnent tous normalement avec les propriétés définies dans une classe Razor Page. Considérez une page qui implémente un formulaire « Nous contacter » de base pour le modèle Contact :

Pour les exemples de ce document, le DbContext est initialisé dans le fichier Startup.cs.

La base de données en mémoire nécessite le package NuGet Microsoft.EntityFrameworkCore.InMemory.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CustomerDbContext>(options =>
                      options.UseInMemoryDatabase("name"));
    services.AddRazorPages();
}

Le modèle de données :

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

Le contexte de la base de données :

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; }
    }
}

Le fichier de vue Pages/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Le modèle de page Pages/Create.cshtml.cs :

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;

        public CreateModel(CustomerDbContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Customers.Add(Customer);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

Par convention, la classe PageModel se nomme <PageName>Model et se trouve dans le même espace de noms que la page.

La classe PageModel permet de séparer la logique d’une page de sa présentation. Elle définit des gestionnaires de page pour les demandes envoyées à la page et les données utilisées pour l’afficher. Cette séparation permet :

La page a une méthode de gestionnaireOnPostAsync, qui s’exécute sur les requêtes POST (quand un utilisateur poste le formulaire). Vous pouvez ajouter des méthodes de gestionnaire pour n’importe quel verbe HTTP. Les gestionnaires les plus courants sont :

  • OnGet pour initialiser l’état nécessaire pour la page. Dans le code précédent, la méthode OnGet affiche la page RazorCreateModel.cshtml.
  • OnPost pour gérer les envois de formulaire.

Le suffixe de nommage Async est facultatif, mais souvent utilisé par convention pour les fonctions asynchrones. Le code précédent est typique de Razor Pages.

Si vous êtes familiarisé avec les applications ASP.NET utilisant des contrôleurs et des vues :

  • Le code de OnPostAsync dans l’exemple précédent est similaire à du code généralement utilisé dans un contrôleur.
  • La plupart des primitives MVC comme la liaison de modèle, la validation et les résultats des actions fonctionnent de la même façon avec les contrôleurs et Razor Pages.

La méthode OnPostAsync précédente :

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Customers.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Le flux de base de OnPostAsync :

Vérifiez s’il y a des erreurs de validation.

  • S’il n’y a aucune erreur, enregistrez les données et redirigez.
  • S’il y a des erreurs, réaffichez la page avec des messages de validation. Dans de nombreux cas, les erreurs de validation seraient détectées sur le client et jamais envoyées au serveur.

Le fichier de vue Pages/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Le HTML rendu à partir de Pages/Create.cshtml :

<p>Enter a customer name:</p>

<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>

Dans le code précédent, le formulaire est publié :

  • Avec des données valides :

    • La méthode de gestionnaire OnPostAsync appelle la méthode d’assistance RedirectToPage. RedirectToPage retourne une instance de RedirectToPageResult. RedirectToPage:

      • Est un résultat de l’action.
      • Est similaire à RedirectToAction ou à RedirectToRoute (utilisé dans les contrôleurs et les vues).
      • Est personnalisé pour les pages. Dans l’exemple précédent, il redirige vers la page Index racine (/Index). RedirectToPage est détaillé dans la section Génération d’URL pour les pages.
  • Avec les erreurs de validation passées au serveur :

    • La méthode de gestionnaire OnPostAsync appelle la méthode d’assistance Page. Page retourne une instance de PageResult. Le retour de Page est similaire à la façon dont les actions dans les contrôleurs retournent View. PageResult est le type de retour par défaut pour une méthode de gestionnaire. Une méthode de gestionnaire qui retourne void restitue la page.
    • Dans l’exemple précédent, la publication du formulaire sans valeur fait que ModelState.IsValid retourne false. Dans cet exemple, aucune erreur de validation n’est affichée sur le client. La gestion des erreurs de validation est traitée plus loin dans ce document.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • Avec les erreurs de validation détectées par la validation côté client :

    • Les données ne sont pas publiées sur le serveur.
    • La validation côté client est expliquée plus loin dans ce document.

La propriété Customer utilise l’attribut [BindProperty] pour accepter la liaison de modèle :

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

[BindProperty] ne doit pas être utilisé sur les modèles contenant des propriétés qui ne doivent pas être modifiées par le client. Pour plus d’informations, consultez Surpublication.

Par défaut, Razor Pages lie les propriétés seulement avec des verbes non-GET. La liaison à des propriétés supprime la nécessité d’écrire du code pour convertir des données HTTP dans le type du modèle. Elle réduit la quantité de code en utilisant la même propriété pour afficher les champs de formulaire (<input asp-for="Customer.Name">) et accepter l’entrée.

Avertissement

Pour des raisons de sécurité, vous devez choisir de lier les données de requête GET aux propriétés du modèle de page. Vérifiez l’entrée utilisateur avant de la mapper à des propriétés. Le choix de la liaison GET convient pour les scénarios qui s’appuient sur des valeurs de routage ou de chaîne de requête.

Pour lier une propriété sur des requêtes GET, définissez la propriété SupportsGet de l’attribut [BindProperty] sur true :

[BindProperty(SupportsGet = true)]

Pour plus d’informations, consultez ASP.NET Core Community Standup: Bind on GET discussion (YouTube).

Examen du fichier de vue Pages/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>
  • Dans le code précédent, le Tag Helper d’entrée<input asp-for="Customer.Name" /> lie l’élément HTML <input> à l’expression de modèle Customer.Name.
  • @addTagHelper rend les Tag Helpers disponibles.

La page d’accueil

Index.cshtml est la page d’accueil :

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customer)
            {
                <tr>
                    <td> @contact.Id  </td>
                    <td>@contact.Name</td>
                    <td>
                        <!-- <snippet_Edit> -->
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <!-- </snippet_Edit> -->
                        <!-- <snippet_Delete> -->
                        <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                        <!-- </snippet_Delete> -->
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

La classe PageModel associée (Index.cshtml.cs) :

public class IndexModel : PageModel
{
    private readonly CustomerDbContext _context;

    public IndexModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IList<Customer> Customer { get; set; }

    public async Task OnGetAsync()
    {
        Customer = await _context.Customers.ToListAsync();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customers.FindAsync(id);

        if (contact != null)
        {
            _context.Customers.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

Le fichier Index.cshtml contient le balisage suivant :

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

Le <a /a>Tag Helper d’ancre a utilisé l’attribut asp-route-{value} pour générer un lien vers la page Edit. Le lien contient des données d’itinéraire avec l’ID de contact. Par exemple : https://localhost:5001/Edit/1. Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu des éléments HTML dans les fichiers Razor.

Le fichier Index.cshtml contient également le balisage pour créer un bouton Delete (Supprimer) pour chaque contact client :

<button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>

Le HTML rendu :

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

Quand le bouton Delete est rendu en HTML, son action de formulaire inclut des paramètres pour :

  • L’ID du contact client spécifié par l’attribut asp-route-id.
  • Le handler spécifié par l’attribut asp-page-handler.

Quand le bouton est sélectionné, une demande POST de forumaire est envoyée au serveur. Par convention, le nom de la méthode de gestionnaire est sélectionné en fonction de la valeur du paramètre handler conformément au schéma OnPost[handler]Async.

Étant donné que le handler est delete dans cet exemple, la méthode de gestionnaire OnPostDeleteAsync est utilisée pour traiter la demande POST. Si asp-page-handler est défini sur une autre valeur, comme remove, une méthode de gestionnaire avec le nom OnPostRemoveAsync est sélectionnée.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customers.FindAsync(id);

    if (contact != null)
    {
        _context.Customers.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

La méthode OnPostDeleteAsync :

  • Obtient l’id de la chaîne de requête.
  • Interroge la base de données pour le contact client avec FindAsync.
  • Si le contact client est trouvé, il est supprimé et la base de données est mise à jour.
  • Appelle RedirectToPage pour rediriger vers la page Index racine (/Index).

Le fichier Edit.cshtml

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
    <div asp-validation-summary="All"></div>
    <input asp-for="Customer.Id" type="hidden" />
    <div>
        <label asp-for="Customer.Name"></label>
        <div>
            <input asp-for="Customer.Name" />
            <span asp-validation-for="Customer.Name"></span>
        </div>
    </div>

    <div>
        <button type="submit">Save</button>
    </div>
</form>

La première ligne contient la directive @page "{id:int}". La contrainte de routage "{id:int}" indique à la page qu’elle doit accepter les requêtes qui contiennent des données de routage int. Si une requête à la page ne contient de données d’itinéraire qui peuvent être converties en int, le runtime retourne une erreur HTTP 404 (introuvable). Pour que l’ID soit facultatif, ajoutez ? à la contrainte d’itinéraire :

@page "{id:int?}"

Le fichier Edit.cshtml.cs :

public class EditModel : PageModel
{
    private readonly CustomerDbContext _context;

    public EditModel(CustomerDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Customer = await _context.Customers.FindAsync(id);

        if (Customer == null)
        {
            return RedirectToPage("./Index");
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            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

Les règles de validation :

  • Sont spécifiées de manière déclarative dans la classe de modèle.
  • Sont appliquées partout dans l’application.

L’espace de noms System.ComponentModel.DataAnnotations fournit un ensemble d’attributs de validation intégrés qui sont appliqués de façon déclarative à une classe ou à une propriété. DataAnnotations contient également des attributs de mise en forme, comme [DataType], qui aident à effectuer la mise en forme et ne fournissent aucune validation.

Considérons le modèle Customer :

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

En utilisant le fichier de vue suivant Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<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>

Le code précédent :

  • Inclut jQuery et des scripts de validation jQuery.

  • Utilise le <div /> et <span />Tag Helpers pour permettre :

    • La validation côté client.
    • Le rendu des erreurs de validation.
  • Génère le code HTML suivant :

    <p>Enter a customer name:</p>
    
    <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>
    

La publication du formulaire Create (Créer) sans valeur pour le nom affiche le message d’erreur « The Name field is required. » (Le champ Nom est requis.) sur le formulaire. Si JavaScript est activé sur le client, le navigateur affiche l’erreur sans publier sur le serveur.

L’attribut [StringLength(10)] génère data-val-length-max="10" sur le HTML rendu. data-val-length-max empêche les navigateurs d’autoriser une entrée supérieure à la longueur maximale spécifiée. Si un outil comme Fiddler est utilisé pour modifier et refaire la publication :

  • Avec le nom d’une longueur supérieure à 10.
  • Le message d’erreur « The field Name must be a string with a maximum length of 10. » (Le nom du champ doit être une chaîne dont la longueur maximale est de 10.) est retourné.

Considérez le modèle Movie suivant :

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [StringLength(60, MinimumLength = 3)]
        [Required]
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [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; }
    }
}

Les attributs de validation spécifient le comportement à appliquer sur les propriétés du modèle auxquelles ils sont appliqués :

  • Les attributs Required et MinimumLength indiquent qu’une propriété doit avoir une valeur, mais rien n’empêche un utilisateur d’entrer un espace pour satisfaire à cette validation.

  • L’attribut RegularExpression sert à limiter les caractères pouvant être entrés. Dans le code précédent, « Genre » :

    • Doit utiliser seulement des lettres.
    • La première lettre doit être une majuscule. Les espaces, les chiffres et les caractères spéciaux ne sont pas autorisés.
  • L’expression RegularExpression « Rating » :

    • Nécessite que le premier caractère soit une lettre majuscule.
    • Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent. « PG-13 » est valide pour une évaluation, mais échoue pour un « Genre ».
  • L’attribut Range limite une valeur à une plage spécifiée.

  • L’attribut StringLength définit la longueur maximale d’une propriété de chaîne, et éventuellement sa longueur minimale.

  • Les types valeur (tels que decimal, int, float et DateTime) sont obligatoires par nature et n’ont pas besoin de l’attribut [Required].

La page Create pour le Movie modèle montre les erreurs avec des valeurs non valides :

Formulaire de vue Movie avec plusieurs erreurs de validation jQuery côté client

Pour plus d'informations, consultez les pages suivantes :

Gérer les requêtes HEAD avec un gestionnaire OnGet de secours

Les demandes HEAD vous permettent de récupérer les en-têtes pour une ressource spécifique. Contrairement aux requêtes GET, les requêtes HEAD ne retournent pas un corps de réponse.

En règle générale, un gestionnaire OnHead est créé et appelé pour les requêtes HEAD :

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

Razor Pages se rabat sur un appel du gestionnaire OnGet si aucun gestionnaire OnHead n’est défini.

XSRF/CSRF et Razor Pages

Les pages Razor sont protégées par la validation antifalsification. FormTagHelper injecte des jetons antifalsification dans les éléments de formulaire HTML.

Utilisation de dispositions, de lignes de code partiellement exécutées, de modèles et de Tag Helpers avec Razor Pages

Les pages fonctionnent avec toutes les fonctionnalités du moteur de vue Razor. Les dispositions, les lignes de code partiellement exécutées, les modèles, les Tag Helpers, _ViewStart.cshtml et _ViewImports.cshtml fonctionnent de la même façon que pour les vues Razor conventionnelles.

Nous allons nettoyer un peu cette page en tirant parti de certaines de ces fonctionnalités.

Ajoutez une page de disposition à Pages/Shared/_Layout.cshtml :

<!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>

La disposition :

  • Contrôle la disposition de chaque page (à moins que la page ne refuse la disposition).
  • Importe des structures HTML telles que JavaScript et les feuilles de style.
  • Le contenu de la page Razor est rendu là où @RenderBody() est appelé.

Pour plus d’informations, consultez Page de disposition.

La propriété Layout est définie dans Pages/_ViewStart.cshtml :

@{
    Layout = "_Layout";
}

La disposition est dans le dossier Pages/Shared. Les pages recherchent d’autres vues (dispositions, modèles, partiels) hiérarchiquement, en commençant dans le même dossier que la page active. Une disposition dans le dossier Pages/Shared peut être utilisée depuis n’importe quelle page Razor sous le dossier Pages.

Le fichier de disposition doit être placé dans le dossier Pages/Shared.

Nous vous recommandons de ne pas placer le fichier de disposition dans le dossier Views/Shared. Views/Shared est un modèle de vues MVC. Les pages Razor sont censées s’appuyer sur la hiérarchie des dossiers, et non pas sur les conventions des chemins.

La recherche des vues depuis une page Razor inclut le dossier Pages. Les dispositions, les modèles et les lignes de code partiellement exécutées que vous utilisez avec les contrôleurs MVC et les vues Razor conventionnelles fonctionnent normalement.

Ajoutez un fichier Pages/_ViewImports.cshtml :

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace est expliqué plus loin dans le didacticiel. La directive @addTagHelper permet de bénéficier des Tag Helpers intégrés dans toutes les pages du dossier Pages.

La directive @namespace définie sur une page :

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

La directive @namespace définit l’espace de noms pour la page. La directive @model n’a pas besoin d’inclure l’espace de noms.

Quand la directive @namespace est contenue dans _ViewImports.cshtml, l’espace de noms spécifié fournit le préfixe de l’espace de noms généré dans la Page qui importe la directive @namespace. Le reste de l’espace de noms généré (la partie suffixe) est le chemin relatif avec le point comme séparateur entre le dossier contenant _ViewImports.cshtml et le dossier contenant la page.

Par exemple, la classe PageModel de Pages/Customers/Edit.cshtml.cs définit explicitement l’espace de noms :

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

Le fichier Pages/_ViewImports.cshtml définit l’espace de noms suivant :

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

L’espace de noms généré pour la page RazorPages/Customers/Edit.cshtml est identique à la classe PageModel.

@namespace fonctionne également avec les vues Razor conventionnelles.

Considérez le fichier de vue Pages/Create.cshtml :

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<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>

Le fichier de vue Pages/Create.cshtml mis à jour avec _ViewImports.cshtml et le fichier de disposition précédent :

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Dans le code précédent, _ViewImports.cshtml a importé l’espace de noms et des Tag Helpers. Le fichier de disposition a importé les fichiers JavaScript.

Le projet de démarrage Razor Pages contient le Pages/_ValidationScriptsPartial.cshtml, qui connecte la validation côté client.

Pour plus d’informations sur les vues partielles, consultez Vues partielles dans ASP.NET Core.

Génération d’URL pour les pages

La page Create, illustrée précédemment, utilise RedirectToPage :

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

        return RedirectToPage("./Index");
    }
}

L’application a la structure de fichiers/dossiers suivante :

  • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

      • Create.cshtml
      • Edit.cshtml
      • Index.cshtml

Les pages Pages/Customers/Create.cshtml et Pages/Customers/Edit.cshtml redirigent vers Pages/Customers/Index.cshtml après réussite. La chaîne ./Index est un nom de page relatif utilisé pour accéder à la page précédente. Il est utilisé pour générer des URL vers la page Pages/Customers/Index.cshtml. Par exemple :

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

Le nom de page absolu /Index est utilisé pour générer des URL vers la page Pages/Index.cshtml. Par exemple :

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

Le nom de la page est le chemin de la page à partir du dossier racine /Pages avec un / devant (par exemple, /Index). Les exemples de génération d’URL précédents offrent des options améliorées et des capacités fonctionnelles par rapport au codage en dur d’une URL. La génération d’URL utilise le routage et peut générer et encoder des paramètres en fonction de la façon dont l’itinéraire est défini dans le chemin de destination.

La génération d’URL pour les pages prend en charge les noms relatifs. Le tableau suivant montre la page Index sélectionnée en utilisant différents paramètres RedirectToPage dans Pages/Customers/Create.cshtml.

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") et RedirectToPage("../Index") sont des noms relatifs. Le paramètre RedirectToPage est combiné avec le chemin de la page active pour calculer le nom de la page de destination.

La liaison de nom relatif est utile lors de la création de sites avec une structure complexe. Quand des noms relatifs sont utilisés pour lier des pages dans un dossier :

  • Le renommage d’un dossier ne rompt pas les liens relatifs.
  • Les liens ne sont pas rompus, car ils n’incluent pas le nom du dossier.

Pour rediriger vers une page située dans une autre Zone, spécifiez la zone :

RedirectToPage("/Index", new { area = "Services" });

Pour plus d’informations, consultez Zones dans ASP.NET Core et Conventions des routes et des applications Razor dans ASP.NET Core.

Attribut ViewData

Des données peuvent être passées à une page avec ViewDataAttribute. Les valeurs des propriétés ayant l’attribut [ViewData] sont stockées dans le ViewDataDictionary et chargées depuis celui-ci.

Dans l’exemple suivant, AboutModel applique l’attribut [ViewData] à la propriété Title :

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

Dans la page À propos de, accédez à la propriété Title en tant que propriété de modèle :

<h1>@Model.Title</h1>

Dans la disposition, le titre est lu à partir du dictionnaire ViewData :

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempData

ASP.NET Core expose le TempData. Cette propriété stocke les données jusqu’à ce qu’elles soient lues. Vous pouvez utiliser les méthodes Keep et Peek pour examiner les données sans suppression. TempData est utile pour la redirection, quand des données sont nécessaires pour plusieurs requêtes.

Le code suivant définit la valeur de Message à l’aide de TempData :

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

Le balisage suivant dans le fichier Pages/Customers/Index.cshtml affiche la valeur de Message en utilisant TempData.

<h3>Msg: @Model.Message</h3>

Le modèle de page Pages/Customers/Index.cshtml.cs applique l’attribut [TempData] à la propriété Message.

[TempData]
public string Message { get; set; }

Pour plus d’informations, consultez TempData.

Plusieurs gestionnaires par page

La page suivante génère un balisage pour deux gestionnaires en utilisant le Tag Helper asp-page-handler :

@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>
        <!-- <snippet_Handlers> -->
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
        <!-- </snippet_Handlers> -->
    </form>
</body>
</html>

Le formulaire dans l’exemple précédent contient deux boutons d’envoi, chacun utilisant FormActionTagHelper pour envoyer à une URL différente. L’attribut asp-page-handler est un complément de asp-page. asp-page-handler génère des URL qui envoient à chacune des méthodes de gestionnaire définies par une page. asp-page n’est pas spécifié, car l’exemple établit une liaison à la page active.

Le modèle de 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;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

        [BindProperty]
        public Customer Customer { get; set; }

        public async Task<IActionResult> OnPostJoinListAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

Le code précédent utilise des méthodes de gestionnaire nommées. Pour créer des méthodes de gestionnaire nommées, il faut prendre le texte dans le nom après On<HTTP Verb> et avant Async (le cas échéant). Dans l’exemple précédent, les méthodes de page sont OnPostJoinListAsync et OnPostJoinListUCAsync. Avec OnPost et Async supprimés, les noms des gestionnaires sont JoinList et JoinListUC.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

Avec le code précédent, le chemin d’URL qui envoie à OnPostJoinListAsync est https://localhost:5001/Customers/CreateFATH?handler=JoinList. Le chemin d’URL qui envoie à OnPostJoinListUCAsync est https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

Itinéraires personnalisés

Utilisez la directive @page pour :

  • Spécifier une route personnalisée vers une page. Par exemple, la route vers la page À propos peut être définie sur /Some/Other/Path avec @page "/Some/Other/Path".
  • Ajouter des segments à la route par défaut d’une page. Par exemple, un segment « item » peut être ajouté à la route par défaut d’une page avec @page "item".
  • Ajouter des paramètres à la route par défaut d’une page. Par exemple, un paramètre d’ID, id, peut être nécessaire pour une page avec @page "{id}".

Un chemin relatif racine désigné par un tilde (~) au début du chemin est pris en charge. Par exemple, @page "~/Some/Other/Path" est identique à @page "/Some/Other/Path".

Si vous ne voulez pas avoir la chaîne de requête ?handler=JoinList dans l’URL, changez la route pour placer le nom du gestionnaire dans la partie « chemin » de l’URL. La route peut être personnalisée en ajoutant un modèle de route placé entre des guillemets doubles après la directive @page.

@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>

Avec le code précédent, le chemin d’URL qui envoie à OnPostJoinListAsync est https://localhost:5001/Customers/CreateFATH/JoinList. Le chemin d’URL qui envoie à OnPostJoinListUCAsync est https://localhost:5001/Customers/CreateFATH/JoinListUC.

Le ? suivant handler signifie que le paramètre d’itinéraire est facultatif.

Configuration et paramètres avancés

La configuration et les paramètres des sections suivantes ne sont pas nécessaires pour la plupart des applications.

Pour configurer les options avancées, utilisez la surcharge AddRazorPages qui configure RazorPagesOptions :

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
    {
        options.RootDirectory = "/MyPages";
        options.Conventions.AuthorizeFolder("/MyPages/Admin");
    });
}

Utilisez RazorPagesOptions pour définir le répertoire racine pour les pages ou ajouter des conventions de modèle d’application pour les pages. Pour plus d’informations sur les conventions, consultez Conventions des autorisations de Razor Pages.

Pour précompiler des vues, consultez Compilation des vues Razor.

Spécifier que les pages Razor se trouvent à la racine du contenu

Par défaut, les pages Razor sont placées à la racine du répertoire /Pages. Ajoutez WithRazorPagesAtContentRoot pour spécifier que vos pages Razor se trouvent à la racine du contenu (ContentRootPath) de l’application :

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesAtContentRoot();
}

Spécifier que les pages Razor se trouvent dans un répertoire racine personnalisé

Ajoutez WithRazorPagesRoot pour spécifier que vos pages Razor se trouvent dans un répertoire racine personnalisé de l’application (fournissez un chemin relatif) :

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesRoot("/path/to/razor/pages");
}

Ressources supplémentaires