Skip to content

07 - Razor Pages

HTTP - GET

HTTP is purely text based protocol, basically you are just sending bunch of key-value pairs from browser to server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GET https://localhost:44363/People/Create HTTP/1.1
Host: localhost:44363
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://localhost:44363/People
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,et;q=0.8
Cookie: _ga=GA1.1.1433392067.1524809543; 

GET Response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcYWthdmVyXHNvdXJjZVxyZXBvc1xIVFRQVGVzdFxXZWJBcHBcUGVvcGxlXENyZWF0ZQ==?=
X-Powered-By: ASP.NET
Date: Sat, 26 Jan 2019 12:24:13 GMT
Content-Length: 3367

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Create - WebApp</title>
    ...

HTTP - POST

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
POST https://localhost:44363/People/Create HTTP/1.1
Host: localhost:44363
Connection: keep-alive
Content-Length: 194
Cache-Control: max-age=0
Origin: https://localhost:44363
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://localhost:44363/People/Create
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,et;q=0.8
Cookie: _ga=GA1.1.1433392067.1524809543; 

Name=Andres&__RequestVerificationToken=CfDJ8JXfYWVYzpxPgG_38dOFAzHQGkoaDtNMXtzBr3vNUFUDOYe7XT_ueh6IMyEfRXYWTTOvd2Bjm8gxjOwN8jRBGngeIIKZmnJB-xD4Wvd1enqy0M5GrgAWVgAOg0vZPgHNzvIYDBTmwJqi3L2WYM-5mm4

POST Response

1
2
3
4
5
6
7
HTTP/1.1 302 Found
Location: /People
Server: Microsoft-IIS/10.0
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcYWthdmVyXHNvdXJjZVxyZXBvc1xIVFRQVGVzdFxXZWJBcHBcUGVvcGxlXENyZWF0ZQ==?=
X-Powered-By: ASP.NET
Date: Sat, 26 Jan 2019 12:17:16 GMT
Content-Length: 0

HTTP - Response Codes

  • 1xx Informational response 100 continue, 101 switching protocols
  • 2xx Success
    200 OK, 201 Created, 202 Accepted, 204 No Content
  • 3xx Redirection
    300 Multiple choices, 301 Moved Permanently, 302 Found, 303 See other, 304 Not Modified
  • 4xx Client errors
    400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict
  • 5xx Server errors
    500 Internal Server Error, 501 Not implemented, 503 Service Unavailable

Razor Pages

  • ASP.NET Core MVC – full and complex web application development framework (next semester, in Distributed course)
  • Razor Pages – new aspect of ASP.NET, meant for page-focused workflows It's simpler!

Basics

  • Basic page consists of two files
    • <pagename>.cshtml – HTML/Razor file
    • <pagename>.cshtml.cs – C# code file, containing data and methods for servicing different web verbs (POST, GET and others)
  • Razor
    • Microsoft developed language for mixing C# and HTML

Simplest page

Simplest .cshtml razor page:

1
2
@page
<h1>Hello, world!</h1>

And C# code file (not required):

1
2
3
4
5
6
7
8
9
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorTest.Pages{
    public class SimpleModel : PageModel
    {
        public void OnGet(){}
        public void OnPost(){}
    }
}

Result:

RazorPage

Language

Default language in .cshtml files is HTML.

  • @ - sign in html?
    • @ denotes special Razor syntax – either Razor directive or C# expression/code block (similar to php’s <?php … ?>
    • @page – mandatory Razor directive, has to be on first line!
    • @SomeCSharpMethod() – method is executed, .ToString() applied to result – and then output included into HTML
    • There is no end sign (no ?> like in php)

Adding data to page

Lets add some data to page:

  • @model – model class file for this page
  • @Model – refers to model instance during runtime (strongly typed)
1
2
3
4
@page
@model RazorTest.Pages.SimpleModel
<h1>Hello, world!</h1>
<h2>@Model.Message</h2>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class SimpleModel : PageModel
{
    public string Message { get; private set; } = "Hello from code: ";

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

    public void OnPost(){}
}

Result:

Razor Page

Technical background

Where is all this coming from? Where is the web server? How is the correct page chosen? Cant we have headers, footers, static files etc....

ASP.NET Core MVC applications are Console Apps!

Web applications include built in webserver called Kestrel. Extremely fast and small – but not really meant to be exposed directly to full web. In case of public deployment – proxy is typically used (Apache, IIS, ngnix). Kestrel takes over full ip:port – no multi tenant hosting.

Structure

Default web project structure:

  • wwwroot – static web content (images, css, js). Everything is accessible via browser from /.
  • Pages – razor pages
  • Appsettings.json – settings, db connection string
  • Program.cs – console app, kestrel host
  • Startup.cs – Services and request pipeline

Program.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.
            CreateDefaultBuilder(args).
            UseStartup<Startup>();
}

Startup.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
    {
        app.UseDeveloperExceptionPage();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); });
    }
}

Universal files

Pages folder:

  • _ViewImports.cshtml - Razor directives imported into every page
  • _ViewStart.cshtml – Sets the Layout property for every page
  • Shared/_Layout.cshtml - layout for every page (can be changed per page)

Page filename & URL matching

  • URL path matching is determined by page’s location in file system
  • /Pages/Index.cshtml -> http://.../ or http://.../Index
  • /Pages/Contact.cshtml -> http://../Contact
  • /Pages/Store/Index.cshtml -> http://.../Store/ or http://../Store/Index

Layout.cshtml - Minimal example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>RazorTest</title>
</head>
<body>
        @RenderBody()
</body>
</html>

@RenderBody – output of specific razor page is inserted here

Default layout features

Default layout has many more features:

  • Bootstrap based layout
  • Clientside unobtrusive validation
  • CDN usage with fallback to local files
  • App specific js and css (on top of bootstrap)

Create Domain entity and DAL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace Domain{
    public class Person
    {
        public int PersonId { get; set; }

        [MaxLength(64, ErrorMessage = "Too long!")]
        [MinLength(2, ErrorMessage = "Too short!")]
        public string Name { get; set; }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace DAL{
    public class AppDbContext: DbContext
    {
        public DbSet<Person> Persons { get; set; }
        public AppDbContext(DbContextOptions options)
            : base(options){

            }
    }
}

Register DBContext in services (Dependency Injection)

1
2
3
4
5
6
7
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(options =>
        options.UseInMemoryDatabase("inmemorydb")); 

    services.AddRazorPages();
}

Access database in IndexModel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class IndexModel : PageModel
{
    private AppDbContext _db;

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

    public int ContactCount { get; set; }
    public List<Person> Persons { get; set; }

    public void OnGet()
    {
        ContactCount = _db.Persons.Count();
        Persons = _db.Persons.ToList();
    }
}

Output data in HTML

1
2
3
4
5
6
7
8
9
@page
@model IndexModel

<div>Contacts in db: @Model.ContactCount</div>

@foreach (var person in Model.Persons)
{
    <div>@person.Name</div>
}

Create ContactModel

  • BindProperty – binds data in HTTP post to C# properties
  • Does not work in case of GET, use [BindProperty(SupportsGet = true)]
  • To bind all public properties, use [BindProperties] on top of class.
  • Binding searches for key-value pairs from (and in order):
    Form, Body, Route, Query string, Uploaded files
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ContactModel : PageModel
{
    private readonly AppDbContext _db;
    public ContactModel(AppDbContext db)
    {
        _db = db;
    }

    // bind data on incoming http post request
    [BindProperty]
    public Person Person { get; set; }

    public IActionResult OnPost()
    {
                if (!ModelState.IsValid)
        {
            return Page();
        }

       _db.Persons.Add(Person);
       _db.SaveChanges();

       return RedirectToPage("/Index");
    }
}

Contact.cshtml

1
2
3
4
5
6
7
8
@page
@model ContactModel
<p>Enter your name.</p>
<div asp-validation-summary="All"></div>
<form method="POST">
    <div>FirstName: <input asp-for="Person.Name" /></div>
    <input type="submit" />
</form>