WYSIWYG md editor SimpleMDE or use some desktop markdown text editor    Home    [ html2markdown ]
Markdown txt 01/006_visual_studio/DOT_NET_Core2_1_RazorPagesMovie.txt Parsedown-ed to HTML.    [ Edit text ]    [ Colored text ]

https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start?view=aspnetcore-2.1 -Visual Studio version.
code: https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/tutorials/razor-pages/razor-pages-start

https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages-vsc/index?view=aspnetcore-2.1 - Visual Studio Code version.

11/12/2019 By Rick Anderson

1. New DOT.NET Core 2.1 project "RazorPagesMovie"

  1. File menu -> New > Project Name the project RazorPagesMovie. It`s important so the namespaces will match when you copy/paste code.
  2. J:\awww\www\dncore Select ASP.NET Core 2.1 in the dropdown, and then select Web Application.
  3. Visual Studio template creates a starter project. Press F5 to run the app in debug mode or Ctrl-F5 to run without attaching the debugger. Select Accept to consent to tracking. This app doesn`t track personal information.
  4. https://localhost:44374/
  5. Ctrl+F5 (non-debug mode) allows you to make code changes, save the file, refresh the browser, and see the code changes. Many developers prefer to use non-debug mode to quickly launch the app and view changes.
  6. navigation icon (3 horizontal minuses) to show the links.
  7. right-click the RazorPagesMovie project > Add > New Folder. Name it folder Models
  8. Right click Models folder -> Add > Class. Name it Movie. Replace the contents - see below.
  9. See 3. Scaffold the movie model (cre pages for CRUD) - Create Pages/Movies folder, right click on the Pages/Movies folder > Add > New Scaffolded Item
  10. Create a new class named SeedData in the Models folder, see code below
  11. https://localhost:44374/Movies

Project files and folders

The following table lists the files and folders in the project. For this tutorial, the Startup.cs file is the most important to understand. You don't need to review each link provided below. The links are provided as a reference when you need more information on a file or folder in the project.

File or folder Purpose
wwwroot Contains static assets. See Static files
Pages Folder for Razor Pages
appsettings.json Configuration
Program.cs Configures the host of the ASP.NET Core app
Startup.cs Configures services and the request pipeline. See Startup

Pages/Shared folder

_Layout.cshtml file contains common HTML elements (script and stylesheet links) and sets the layout for the app. For example when you select RazorPagesMovie, Home, About or Contact, a common set of elements appears in the webpage. The common elements include the navigation menu at the top and the header at the bottom of the window. For more information, see Layout.

_ValidationScriptsPartial.cshtml file provides a reference to jQuery validation scripts. When the Create and Edit pages are added later in the tutorial, the _ValidationScriptsPartial.cshtml file is used.

_CookieConsentPartial.cshtml file provides a navigation bar and content to summarize the privacy and cookie use policy. For more information on the GDPR assets included in the project, see EU General Data Protection Regulation (GDPR) support in ASP.NET Core).

The Pages folder

_ViewStart.cshtml sets the Razor Pages Layout property to use the _Layout.cshtml file. See Layout for more information.

_ViewImports.cshtml file contains Razor directives that are imported into each Razor Page. See Importing Shared Directives for more information.

About, Contact and Index pages are basic pages you can use to start an app. The Error page is used to display error information.

Use F7 to toggle between a Razor Page and the PageModel

To enable F7 toggling between a Razor Page (.cshtml file) and the C# file (.cshtml.cs):

  1. Select Tools > Options > Environment > Keyboard
  2. Enter ToggleRazorView in Show commands containing.
  3. Select EditorContextMenus.CodeWindow.ToggleRazorView
  4. In the Press shortcut keys entry box, press F7.
  5. Select Assign > OK

2. Add a model to app

In this section, you add classes for managing movies in a database. You use these classes with Entity Framework Core (EF Core) to work with a database. EF Core is an object-relational mapping (ORM) framework that simplifies the data access code that you have to write.

The model classes you create are known as POCO classes (from "plain-old CLR objects") because they don't have any dependency on EF Core. They define the properties of the data that are stored in the database.

In this tutorial, you write the model classes first, and EF Core creates DB. An alternate approach not covered here is to generate model classes from an existing database.

View or download sample.

Add a data model

In Solution Explorer, right-click the RazorPagesMovie project > Add > New Folder. Name the folder Models.

Right click Models folder -> Add > Class. Name the class Movie and replace the contents of the Movie class with the following code:

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

namespace RazorPagesMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

3. Scaffold the movie model (cre pages for CRUD)

3.1 Scaffolding tool produces pages for (CRUD) operations for the movie model.

CRUD is Create, Read, Update, and Delete table rows.

Create a Pages/Movies folder:

In Solution Explorer, right click on the Pages folder > Add > New Folder. Name the folder Movies

In Solution Explorer, right click on the Pages/Movies folder > Add > New Scaffolded Item.

In the Add Scaffold dialog, select Razor Pages using Entity Framework (CRUD) > Add.

Complete the Add Razor Pages using Entity Framework (CRUD) dialog:

  1. In the Model class drop down, select Movie (RazorPagesMovie.Models).
  2. In the Data context class row, select the + (plus) sign and accept the generated name RazorPagesMovie.Models.RazorPagesMovieContext.
  3. Select Add.

The scaffold process creates and updates the following files:

Files created

Pages/Movies: Create, Delete, Details, Edit, Index. These pages are detailed in the next tutorial. Data/RazorPagesMovieContext.cs

File updated

Startup.cs: Changes to this file are detailed in the next section. appsettings.json: The connection string used to connect to a local database is added.

3.2 Examine the context registered with dependency injection

ASP.NET Core is built with dependency injection. Services (such as the EF Core DB context) are registered with dependency injection during application startup. Components that require these services (such as Razor Pages) are provided these services via constructor parameters. The constructor code that gets a DB context instance is shown later in the tutorial.

The scaffolding tool automatically created a DB context and registered it with the dependency injection container.

Examine the Startup.ConfigureServices method. Last 2 lines was added by the scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

     services.AddDbContext<RazorPagesMovieContext>(options =>
             options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));
}

The main class that coordinates EF Core functionality for a given data model is the DB context class. The data context is derived from Microsoft.EntityFrameworkCore.DbContext. The data context specifies which entities are included in the data model. In this project, the class is named RazorPagesMovieContext.

using Microsoft.EntityFrameworkCore;

namespace RazorPagesMovie.Models
{
    public class RazorPagesMovieContext : DbContext
    {
        public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
            : base(options)
        {
        }

        public DbSet<Movie> Movie { get; set; }
    }
}

The preceding code creates a DbSet Movie property for the entity set. In Entity Framework terminology, an entity set typically corresponds to a database table. An entity corresponds to a row in the table.

The name of the connection string is passed in to the context by calling a method on a DbContextOptions object. For local development, the ASP.NET Core configuration system reads the connection string from the appsettings.json file.

3.3 Perform initial migration

In this section, you use the Package Manager Console (PMC) to:

  1. Add an initial migration.
  2. Update the database with the initial migration.

From the Tools menu, select NuGet Package Manager > Package Manager Console.

In the PMC, enter the following commands:

Add-Migration Initial (To undo this action, use Remove-Migration.)

The EF Core tools version '2.1.1-rtm-30846' is older than that of the runtime '2.1.4-rtm-31024'. Update the tools for the latest features and bug fixes.

Update-Database

Alternatively, the following .NET Core CLI commands can be used from the project folder:

dotnet ef migrations add Initial dotnet ef database update

Ignore the following warning message, which you fix in a later tutorial:
Microsoft.EntityFrameworkCore.Model.Validation[30000] No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'ForHasColumnType()'.

The Add-Migration command generates code to create the initial database schema. The schema is based on the model specified in the RazorPagesMovieContext (In the Data/RazorPagesMovieContext.cs file). The Initial argument is used to name the migrations. You can use any name, but by convention you choose a name that describes the migration. See Introduction to migrations for more information.

The Update-Database command runs the Up method in the Migrations/{time-stamp}_InitialCreate.cs file, which creates the database.

If you get the error:

SqlException: Cannot open database "RazorPagesMovieContext-GUID" requested by the login. The login failed. Login failed for user 'User-name'.

You missed the migrations step.

Test the app

https://localhost:44374/Movies - Run the app (append /movies to the URL in the browser : http://localhost:port/movies).

Test the Create link -works.

Note
You may not be able to enter decimal commas in the Price field. To support jQuery validation for non-English locales that use a comma (",") for a decimal point and for non US-English date formats, you must globalize your app. For globalization instructions, see this GitHub issue.

Test Edit, Details, and Delete links.

If you get a SQL exception, verify you have run migrations and updated the database.

3.4 Create, R=Details, U=Edit, Delete pages

Scaffolded Razor Pages in ASP.NET Core

Examine the Pages/Movies/Index.cshtml.cs Page Model.

Razor Pages are derived from PageModel. By convention, the PageModel-derived class is called PageName Model. The constructor uses dependency injection to add the MovieContext to the page. All the scaffolded pages follow this pattern. See Asynchronous code for more information on asynchronous programing with Entity Framework.

When a request is made for the page, the OnGetAsync method returns a list of movies to the Razor Page. OnGetAsync or OnGet is called on a Razor Page to initialize the state for the page. In this case, OnGetAsync gets a list of movies and displays them.

When OnGet returns void or OnGetAsync returns Task, no return method is used. When the return type is IActionResult or Task IActionResult, a return statement must be provided. For example, the Pages/Movies/Create.cshtml.cs OnPostAsync method.

Examine the Pages/Movies/Index.cshtml Razor Page.

Razor can transition from HTML into C# or into Razor-specific markup. When an @ symbol is followed by a Razor reserved keyword, it transitions into Razor-specific markup, otherwise it transitions into C#.

The @page Razor directive makes the file into an MVC action — which means that it can handle requests. @page must be the first Razor directive on a page. @page is an example of transitioning into Razor-specific markup. See Razor syntax for more information.

Examine the lambda expression used in the following HTML Helper.

The DisplayNameFor HTML Helper inspects the Title property referenced in the lambda expression to determine the display name. The lambda expression is inspected rather than evaluated. That means there is no access violation when model, model.Movie, or model.Movie[0] are null or empty. When the lambda expression is evaluated (for example, with @Html.DisplayFor(modelItem => item.Title)), the model's property values are evaluated.

The @model directive

The @model directive specifies the type of the model passed to the Razor Page. In the preceding example, the @model line makes the PageModel-derived class available to the Razor Page. The model is used in the @Html.DisplayNameFor and @Html.DisplayFor HTML Helpers on the page.

ViewData and layout

Consider the following code:

@page @model RazorPagesMovie.Pages.Movies.IndexModel

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

The preceding highlighted code is an example of Razor transitioning into C#. The { and } characters enclose a block of C# code.

The PageModel base class has a ViewData dictionary property that can be used to add data that you want to pass to a View. You add objects into the ViewData dictionary using a key/value pattern. In the preceding sample, the "Title" property is added to the ViewData dictionary.

The "Title" property is used in the Pages/Shared/_Layout.cshtml file. The following markup shows the first few lines of the _Layout.cshtml file.

The line @Markup removed for brevity.@ is a Razor comment. Unlike HTML comments (), Razor comments are not sent to the client.

Run the app and test the links in the project (Home, About, Contact, Create, Edit, and Delete). Each page sets the title, which you can see in the browser tab. When you bookmark a page, the title is used for the bookmark. Pages/Index.cshtml and Pages/Movies/Index.cshtml currently have the same title, but you can modify them to have different values.

Layout property is set in the Pages/_ViewStart.cshtml file: CSHTML

@{ Layout = "_Layout"; }

The preceding markup sets the layout file to Pages/Shared/_Layout.cshtml for all Razor files under the Pages folder. See Layout for more information.

Update the layout

Change the <title> element in the Pages/Shared/_Layout.cshtml file to use a shorter string.

Find the following anchor element in the Pages/Shared/_Layout.cshtml file.

<a asp-page="/Index" class="navbar-brand">RazorPagesMovie</a>

Replace the preceding element with the following markup.

<a asp-page="/Movies/Index" class="navbar-brand">RpMovie</a>

The preceding anchor element is Tag Helper. In this case, it's the Anchor Tag Helper. The asp-page="/Movies/Index" Tag Helper attribute and value creates a link to the /Movies/Index Razor Page.

Save your changes, and test the app by clicking on the RpMovie link. See Layout.cshtml file in GitHub.

Create page model

Examine the Pages/Movies/Create.cshtml.cs page model

The OnGet method initializes any state needed for the page. The Create page doesn't have any state to initialize, so Page is returned. Later in the tutorial you see OnGet method initialize state. The Page method creates a PageResult object that renders the Create.cshtml page.

The Movie property uses the [BindProperty] attribute to opt-in to model binding. When the Create form posts the form values, the ASP.NET Core runtime binds the posted values to the Movie model.

The OnPostAsync method is run when the page posts form data.

If there are any model errors, the form is redisplayed, along with any form data posted. Most model errors can be caught on the client-side before the form is posted. An example of a model error is posting a value for the date field that cannot be converted to a date. We'll talk more about client-side validation and model validation later in the tutorial.

If there are no model errors, the data is saved, and the browser is redirected to the Index page.

Create Razor Page

Examine the Pages/Movies/Create.cshtml Razor Page file.

Visual Studio displays the <form method="post"> tag in a distinctive font used for Tag Helpers.

The <form method="post"> element is a Form Tag Helper. The Form Tag Helper automatically includes an antiforgery token.

The scaffolding engine creates Razor markup for each field in the model (except the ID) similar to the following:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="Movie.Title" class="control-label"></label>
    <input asp-for="Movie.Title" class="form-control" />
    <span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

The Validation Tag Helpers (<div asp-validation-summary and <span asp-validation-for) display validation errors. Validation is covered in more detail later in this series.

The Label Tag Helper (<label asp-for="Movie.Title" class="control-label"></label>) generates the label caption and for attribute for the Title property.

The Input Tag Helper (<input asp-for="Movie.Title" class="form-control" />) uses the DataAnnotations attributes and produces HTML attributes needed for jQuery Validation on the client-side.

4. Work with SQL Server LocalDB

The MovieContext object handles the task of connecting to the database and mapping Movie objects to database records. The database context is registered with the Dependency Injection container in the ConfigureServices method in the Startup.cs file

See services.AddDbContext<RazorPagesMovieContext>(options => options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));

For more information on the methods used in ConfigureServices, see:

EU General Data Protection Regulation (GDPR) support in ASP.NET Core for CookiePolicyOptions. SetCompatibilityVersion

The ASP.NET Core Configuration system reads the ConnectionString. For local development, it gets the connection string from the appsettings.json file. The name value for the database (Database={Database name}) will be different for your generated code. The name value is arbitrary.

When you deploy the app to a test or production server, you can use an environment variable or another approach to set the connection string to a real SQL Server. See Configuration for more information.

SQL Server Express LocalDB

LocalDB is a lightweight version of the SQL Server Express Database Engine that's targeted for program development. LocalDB starts on demand and runs in user mode, so there's no complex configuration. By default, *LocalDB database creates ".mdf" files in the C:/Users/<user> directory**.

From the View menu, open SQL Server Object Explorer (SSOX).

Right click on the Movie table and select View Designer

Note the key icon next to ID. By default, EF creates a property named ID for the primary key.

Right click on the Movie table and select View Data

Seed the database

Create a new class named SeedData in the Models folder. Replace the generated code with the following: ...

If there are any movies in the DB, the seed initializer returns and no movies are added.

Add the seed initializer

In Program.cs, modify the Main method to do the following:

The following code shows the updated Program.cs file...

A production app would not call Database.Migrate. It's added to the preceding code to prevent the following exception when Update-Database has not been run:

SqlException: Cannot open database "RazorPagesMovieContext-21" requested by the login. The login failed. Login failed for user 'user name'.

Test the app https://localhost:44374/Movies

The app shows the seeded data: works ok

5. Update pages (data presentation - view)

  1. We don't want to see the time (12:00:00 AM in movies list)
  2. ReleaseDate should be Release Date (two words)

Change in Models/Movie.cs :

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

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

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

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

Right click on [DataType(DataType.Date)] - a red squiggly line > Quick Actions and Refactorings.
Select using System.ComponentModel.DataAnnotations;
Visual Studio adds at script top using System.ComponentModel.DataAnnotations;

Right click on [Column(TypeName = "decimal(18, 2)")] - a red squiggly line > Quick Actions and Refactorings on the [Column] atribute
and select using System.ComponentModel.DataAnnotations.Schema;
The [Column(TypeName = "decimal(18, 2)")] data annotation is required so Entity Framework Core can correctly map Price to currency in the database. For more information, see Data Types.

We'll cover DataAnnotations in the next chapter. The Display attribute specifies what to display for the name of a field (in this case "Release Date" instead of "ReleaseDate"). The DataType attribute specifies the type of the data (Date), so the time information stored in the field isn't displayed.

https://localhost:44374/Movies - Browse to Pages/Movies and hover over an Edit link to see the target URL.

The Edit, Details, and Delete links are generated by the Anchor Tag Helper in the Pages/Movies/Index.cshtml file.
Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files. In the preceding code, the AnchorTagHelper dynamically generates the HTML href attribute value from the Razor Page (the route is relative), the asp-page, and the route id (asp-route-id). See URL generation for Pages for more information.

Use View Source from your favorite browser to examine the generated markup. A portion of the generated HTML is shown below:

<td>
  <a href="/Movies/Edit?id=1">Edit</a> |
  <a href="/Movies/Details?id=1">Details</a> |
  <a href="/Movies/Delete?id=1">Delete</a>
</td>

The dynamically-generated links pass the movie ID with a query string (for example, http://localhost:5000/Movies/Details?id=2).

Update the Edit, Details, and Delete Razor Pages to use the "{id:int}" route template. Change the page directive for each of these pages from @page to @page "{id:int}". Run the app and then view source. The generated HTML adds the ID to the path portion of the URL: HTML

<td>
  <a href="/Movies/Edit/1">Edit</a> |
  <a href="/Movies/Details/1">Details</a> |
  <a href="/Movies/Delete/1">Delete</a>
</td>

A request to the page with the "{id:int}" route template that does not include the integer will return an HTTP 404 (not found) error. For example, http://localhost:5000/Movies/Details will return a 404 error. To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Posting and binding review

Examine the Pages/Movies/Edit.cshtml.cs file:

public class EditModel : PageModel
{
    private readonly RazorPagesMovieContext _context;

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

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

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

        Movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);

        if (Movie == null)
        {
            return NotFound();
        }
        return Page();
    }

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

        _context.Attach(Movie).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!_context.Movie.Any(e => e.ID == Movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

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

When an HTTP GET request is made to the Movies/Edit page (for example, http://localhost:5000/Movies/Edit/2):

The OnGetAsync method fetches the movie from the database and returns the Page method.
The Page method renders the Pages/Movies/Edit.cshtml Razor Page. The Pages/Movies/Edit.cshtml file contains the model directive (@model RazorPagesMovie.Pages.Movies.EditModel), which makes the movie model available on the page.
The Edit form is displayed with the values from the movie.

When the Movies/Edit page is posted:

The form values on the page are bound to the Movie property. The [BindProperty] attribute enables Model binding.
C# 

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

If there are errors in the model state (for example, ReleaseDate cannot be converted to a date), the form is posted again with the submitted values.

If there are no model errors, the movie is saved.

The HTTP GET methods in the Index, Create, and Delete Razor pages follow a similar pattern. The HTTP POST OnPostAsync method in the Create Razor Page follows a similar pattern to the OnPostAsync method in the Edit Razor Page.

6. Search

7. New field

8. Validation