【Blazor Server】CSS Isolation & using child components
Tags: csharp, blazor, aspnetcore
Published: 2020-12-18 11:19:47

## Intro
This time, I try CSS isolatio and creating child conponents in a component.

### Environments
* .NET ver.5.0.101

## Base Project
I create a project like them.
I use ASP.NET Core MVC and Blazor Server.

* [【ASP.NET Core】Try Blazor(Blazor Server)](https://dev.to/masanori_msl/asp-net-core-try-blazor-blazor-server-4j34)
* [【ASP.NET Core】【Blazor Server】Try SPA](https://dev.to/masanori_msl/asp-net-core-blazor-server-try-spa-2cd7)

#### HomeController.cs
```cs
using System;
using Microsoft.AspNetCore.Mvc;

namespace BlazorSample.Controllers
{
    public class HomeController: Controller
    {
        [Route("")]
        [Route("{page}")] // <- DON'T DO THIS for CSS Isolation
        public ActionResult OpenPage(string page)
        {
            ViewData["Title"] = $"Page {page}";
            return View("Views/_Host.cshtml");         
        }
    }
}
```
_Layout.cshtml
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <base href="~/" />
</head>
<body>
    @RenderBody()
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
```
#### _Host.cshtml
```cs
@namespace BlazorSample.Views
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
```
#### App.razor
```html
@using Shared
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
```
#### MainLayout.razor
```cs
@inherits LayoutComponentBase
@Body
```
#### DisplayGridPage.razor
```html
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<div id="sheet_area">
</div>
```
## CSS Isolation
From .NET 5, I can create CSS files for each Blazor files.
I have to do these two things.

1.Add CSS link to {ProjectName}.styles.css in _Layout.cshtml or _Host.cshtml.
2.Add {BlazorFileName}.css. For example, if Blazor file name is "DisplayGridPage.razor", the CSS file name is "DisplayGridPage.razor.css".

#### _Layout.cshtml
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <base href="~/" />
    <!-- When the page is loaded, the content of CSS will be changed -->
    <link href="BlazorSample.styles.css" rel="stylesheet" />
</head>
<body>
    @RenderBody()
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
```
#### DisplayGridPage.razor.css
```css
#sheet_area
{
    height: 30vh;
    width: 30vw;
    background-color: aqua;
    display: grid;
}
h1{
    color: blue;
}
```
### Get HTML named CSS?
Unfortunately, the CSS wasn't work.

Because in the Controller class, I routed "localhost:5000/" and "localhost:5000/{page}" to return View.
So "localhost:5000/BlazorSample.styles.css" was also treated as View.

Though I might be able to resolve by using middleware, I decided change the route.

#### HomeController.cs
```cs
...
namespace BlazorSample.Controllers
{
    public class HomeController: Controller
    {
        [Route("")]
        [Route("Pages/{page}")]
        public ActionResult OpenPage(string page)
...
```
### Generated CSS
When I add CSS for MainLayout.razor, the CSS for DisplayGridPage.razor will be generated from MainLayout.razor.css and DisplayGridPage.razor.css

```css
/* _content/BlazorSample/Views/DisplayGridPage.razor.rz.scp.css */
#sheet_area[b-p832tuedyv]
{
    height: 30vh;
    width: 30vw;
    background-color: aqua;
    display: grid;
}
h1[b-p832tuedyv]{
    color: blue;
}
/* _content/BlazorSample/Views/Shared/MainLayout.razor.rz.scp.css */
h1[b-m6a6nzx0h4]{
    color: red;
}
header[b-m6a6nzx0h4]{
    background-color: rosybrown;
}
```
## Using components
Next, I try add child components.
There is no any special way to create and add child components.

1.Create Blazor files(.razor, .razor.cs, .razor.css)

#### Cell.razor
```html
<div class="cell_frame">Hello cell</div>
```
#### Cell.razor.cs
```cs
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Views.Components
{
    public partial class Cell
    {
    }
}
```
#### Cell.razor.css
```
.cell_frame{
    background-color: gray;
    border: 1px solid black;
    color: goldenrod;
}
```

2.Add 1. into the parent component as a HTML element.

#### DisplayGridPage.razor
```html
@page "/"

<h1>Hello, world!</h1>
Welcome to your new app.
<div id="sheet_area">
    @for(var i = 1; i <= 3; i++){
        <BlazorSample.Views.Components.Cell></BlazorSample.Views.Components.Cell>
    }
</div>
```
#### Result
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tcnzs0meubg7bzh1lwvf.jpg)

### Child component's CSS 
In this sample, "BlazorSample.styles.css" includes "MainLayout.razor.css" and "DisplayGridPage.razor.css".
How about "Cell.razor.css" ?

All of them are merged and added into "BlazorSample.styles.css".

#### BlazorSample.styles.css
```css
/* _content/BlazorSample/Views/Components/Cell.razor.rz.scp.css */
.cell_frame[b-64je09gotq]{
    background-color: gray;
    border: 1px solid black;
    color: goldenrod;
}
/* _content/BlazorSample/Views/Components/SheetSelector.razor.rz.scp.css */
/* _content/BlazorSample/Views/DisplayGridPage.razor.rz.scp.css */
#sheet_area[b-p832tuedyv]
{
    height: 30vh;
    width: 30vw;
    background-color: aqua;
    display: grid;
}
h1[b-p832tuedyv]{
    color: blue;
}
/* _content/BlazorSample/Views/Shared/MainLayout.razor.rz.scp.css */
h1[b-m6a6nzx0h4]{
    color: red;
}
header[b-m6a6nzx0h4]{
    background-color: rosybrown;
}
```
### Set values from the parent
When I want to set values into the child components, I can add parameters into them.

#### Cell.razor.cs
```cs
using System;
using Microsoft.AspNetCore.Components;

namespace BlazorSample.Views.Components
{
    public partial class Cell
    {
        [Parameter]
        public int Index { get; set; }
        [Parameter]
        public BlazorSample.Spreadsheets.Cell CellValue{ get; set; }
    }
}
```
#### Cell.razor
```html
<div class="cell_frame" id="cell_@Index">Hello cell@(Index)</div>
```
#### DisplayGridPage.razor.cs
```cs
...
    public partial class DisplayGridPage
    {
        [Parameter]
        public List<BlazorSample.Spreadsheets.Cell> Cells { get; set; }

        public DisplayGridPage()
        {
            Cells = new List<Cell>
            {
                new Cell(1, 1, "Hello"),
                new Cell(1, 2, "World"),
            };
        }
...
```
#### DisplayGridPage.razor
```html
...
<div id="sheet_area">
    @code
    {
        int index = 1;
    }
    @foreach (var cell in Cells)
    {
        <BlazorSample.Views.Components.Cell Index="index" CellValue="cell">
        </BlazorSample.Views.Components.Cell>
        index += 1;
    }
</div>
...
```
#### When the parameter values will be set?
When the constructor is called, the parameters has not been set their values.
If I want to do something on initializing, I have to override Blazor event methods.

#### Cell.razor.cs
```cs
...
    public partial class Cell
    {
        [Parameter]
        public int Index { get; set; }
        [Parameter]
        public BlazorSample.Spreadsheets.Cell CellValue{ get; set; }
        public Cell()
        {
            // the values haven't set yet.
            // Output: [Constructor] CellValue Index: 0 Cell?:True
            Console.WriteLine($"[Constructor] CellValue Index: {Index} Cell?:{CellValue == null}" );
        }
        protected override void OnInitialized()
        {
            // Output: [Init] CellValue Index: 1 Cell?:False
            Console.WriteLine($"[Init] CellValue Index: {Index} Cell?:{CellValue == null}" );
        }
    }
...
```

I think Blazor's grammer is similar to Angular.

### Callback
I can use "EventCallback" to call from child components to the parent component.

#### Cell.razor.cs
```cs
...
    public partial class Cell
    {
...
        [Parameter]
        public EventCallback<string> OnCellClicked { get; set; }
        public async Task OnClick()
        {
            await OnCellClicked.InvokeAsync();
        }
    }
...
```
#### DisplayGridPage.razor
```html
...
    @foreach (var cell in Cells)
    {
        <BlazorSample.Views.Components.Cell Index="index" CellValue="cell"
            OnCellClicked="@((message) => Console.WriteLine(message))">
        </BlazorSample.Views.Components.Cell>
        index += 1;
    }
...
```

### Escape "" in Blazor
I can use two ways to use ""(double quotations) in Blazor files.

```html
        <!-- Use @() -->
        <BlazorSample.Views.Components.Cell Index="index" CellValue="cell"
            OnCellClicked="@(_ => Console.WriteLine("World"))">
        </BlazorSample.Views.Components.Cell>
        <!-- Use ''(single quotations) -->
        <BlazorSample.Views.Components.Cell Index="index" CellValue="cell"
            OnCellClicked='_ => Console.WriteLine("World")'>
        </BlazorSample.Views.Components.Cell>
```

### Call child components' methods
I can control child components' instances by @ref.

#### Cell.razor.cs
```cs
...
    public partial class Cell
    {
...
        public void Greet()
        {
            Console.WriteLine("Hello World!");
        }
...
```
#### DisplayGridPage.razor
```html
...
    <BlazorSample.Views.Components.Cell Index="10" 
            CellValue='new BlazorSample.Spreadsheets.Cell(10, 10, "HelloWorld")'
            OnCellClicked='_ => Console.WriteLine("World")' @ref="cell">
        </BlazorSample.Views.Components.Cell>
</div>
<button @onclick="ClickSample">Click</button>
@code
{
    BlazorSample.Views.Components.Cell cell;
    void ClickSample()
    {
        cell.Greet();
    }
}
```

### Resources
#### CSS
* [ASP.NET Core Blazor CSS isolation | Microsoft Docs](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-5.0)
* [How to ignore routes in ASP.NET Core? - Stack Overflow](https://stackoverflow.com/questions/39517816/how-to-ignore-routes-in-asp-net-core)
* [Use CSS isolation in your Blazor projects | Dave Brock](https://daveabrock.com/2020/09/10/blazor-css-isolation)

#### Components
* [ASP.NET Core Blazor event handling | Microsoft Docs](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-5.0)
* [Build reusable UI components with Blazor | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/architecture/blazor-for-web-forms-developers/components)
* [ASP.NET Core Blazor lifecycle | Microsoft Docs](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-5.0)
* [how to call child component method from parent component in blazor? - Stack Overflow](https://stackoverflow.com/questions/60041619/how-to-call-child-component-method-from-parent-component-in-blazor)