DEV Community 👩‍💻👨‍💻

Masui Masanori
Masui Masanori

Posted on

【ASP.NET Core】【Blazor Server】Try SPA

Intro

This time I will try creating Single Page Application.
I want to know how to route Blazor pages, and if I can create hierarchical components, and etc..

Routing

First, I add "localhost:5000/" and "localhost:5000/{PageName}" as Blazor page routes.

HomeController.cs

...
        [Route("/")]
        [Route("/{page}")]
        public ActionResult OpenPage(string page)
        {
            return View("Views/_Host.cshtml");            
        }
...
Enter fullscreen mode Exit fullscreen mode

_Host.cshtml just calls a Blazor class for routing.

_Host.cshtml

@using BlazorSample.Views;
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
Enter fullscreen mode Exit fullscreen mode

App.razor

@using BlazorSample.Views.Shared;
<Router AppAssembly="@typeof(Startup).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
Enter fullscreen mode Exit fullscreen mode

MainLayout.razor is common layout for Blazor.
It's as same as _Layout.cshtml for Razor.

MainLayout.razor

@inherits LayoutComponentBase
@Body
Enter fullscreen mode Exit fullscreen mode

SearchPage.razor

@page "/SearchPage";

<input type="text" @bind="productName">
<button @onclick="UpdateValue">Update</button>
@code{
    public string productName = "";

    public async Task UpdateValue()
    {
        productName = "Hello World!";
    }
}
Enter fullscreen mode Exit fullscreen mode

In Blazor, the page path is decided by "@page".
So I can access the SearchPage with "localhost:5000/SearchPage".

_Layout.cshtml

Now, the structure of SearchPage like below.
Alt Text

But if you don't use any other razor pages, you can remove "_ViewStart.cshtml" and merge "_Layout.cshtml" into "_Host.cshtml".

_Host.cshtml

@using BlazorSample.Views;

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>@ViewData["Title"]</title>
        <base href="/">
        <script type="text/javascript">
            if (/MSIE \d|Trident.*rv:/.test(navigator.userAgent)) {
                document.write('<script src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.closest%2CIntersectionObserver%2Cdocument.querySelector%2Cfeatures=Array.prototype.forEach%2CNodeList.prototype.forEach"><\/script>');
                document.write('<script src="js/blazor.polyfill.min.js"><\/script>');
            }
        </script>
    </head>
    <body>        
        <div>Hello Host</div>
        @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
        <script src="_framework/blazor.server.js"></script>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

I also can write common layout in "MainLayout.razor".
But because I can't write <script>, I have to write it in "_Layout.cshtml" or "_Host.csthml".

ViewData

Because Blazor can't access "ViewData" and "ViewBag", so I have to control them in Controller classes or Razor files.

HomeController.cs

...
        [Route("/")]
        [Route("/{page}")]
        public ActionResult OpenPage(string page)
        {
            ViewData["Title"] = GetTitle(page);
            return View("Views/_Host.cshtml");            
        }
        private string GetTitle(string page)
        {
            switch(page)
            {
                case "SearchPage":
                    return "Search";
                default:
                    return "Home";
            }
        }
...
Enter fullscreen mode Exit fullscreen mode

Send data from Router to Component

Last time, I could send date from Razor to Blazor by "[Parameter]".
How about from Router?

It is as same as from Razor to Blazor.

App.razor

@using BlazorSample.Views.Shared;
<Router AppAssembly="@typeof(Startup).Assembly">
    <Found Context="routeData">
        @{
            var values = routeData.RouteValues as Dictionary<string, object> ?? new Dictionary<string, object>() ;
            values.Add("Name", "Hello World");

            var newRouteData = new RouteData(routeData.PageType, values);
        }
        <RouteView RouteData="@newRouteData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
...
</Router>
Enter fullscreen mode Exit fullscreen mode

Similarly, I can set default page when the route isn't found.

@using BlazorSample.Views.Shared;
<Router AppAssembly="@typeof(Startup).Assembly">
...
    <NotFound>
        @{
            var routeData = new RouteData(typeof(SearchPage), new Dictionary<string, object>());
        }
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </NotFound>
</Router>
Enter fullscreen mode Exit fullscreen mode

Create hierarchical components

In Angular, a component can have child components.
Alt Text

How about Blazor?
It's just as same as Angular.

SearchPage.razor

...
@foreach (var item in Products)
{
    <SearchResultRow Product=item></SearchResultRow>
}
...
Enter fullscreen mode Exit fullscreen mode

SearchResultRow.razor

@using Models;
<div class="search_result_row">
    <div class="search_result_row_id">@Product.Id</div>
    <div class="search_result_row_name">@Product.Name</div>
</div>
@code{
    [Parameter]
    public Product Product {get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Separate C# codes from Blazor HTML

When I create small components, I can write both HTML and C# codes in one file.
But when it become more larger and complicated, I want to separate them.

I can move C# codes by partial class.

SearchPage.razor.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Models;

namespace BlazorSample.Views
{
    public partial class SearchPage
    {
        [Inject]
        public Services.IBlazorService Blazor{ get; set; }
        [Parameter]
        public string Name {get; set;}
        public string productName = "";
        public List<Product> Products = new List<Product>
        {
            new Product
            {
                Id = 0,
                Name = "Hello",
            },
            new Product
            {
                Id = 1,
                Name = "World",
            },
        };
        public async Task UpdateValue()
        {
            var product = await Blazor.GetMessageAsync("Hello");
            Console.WriteLine(productName);
            Console.WriteLine(Name);
            productName = product.Name;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now I can remove C# codes from "SearchPage.razor".

SearchPage.razor

@page "/SearchPage";

<input type="text" @bind="productName">
<button @onclick="UpdateValue">Update</button>
@foreach (var item in Products)
{
    <SearchResultRow Product=item></SearchResultRow>
}
Enter fullscreen mode Exit fullscreen mode

One important thing is making those namespaces the same.
Or "SearchPage.razor" can't find and get compiling errors.

Top comments (0)

Take Your Github Repository To The Next Level

>> Check out this classic DEV post <<