vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Blazor Server】【ASP.NET Core】CSS isolation と MapControllers

はじめに

この記事は Blazor Advent Calendar 2020 の16日目の記事です。

.NET 5 で追加された CSS Isolation(CSS の分離)を試してみることにしました。

元のプロジェクト

プロジェクト自体は(バージョンは異なりますが)この時のものをベースにしています。

Razor および Blazor は、 Controller クラスから返しています。

HomeController.cs

using System;
using Microsoft.AspNetCore.Mvc;

namespace BlazorSample.Controllers
{
    public class HomeController: Controller
    {
        [Route("")]
        [Route("{page}")] // <- 後述しますがこれだと問題が発生します
        public ActionResult OpenPage(string page)
        {
            ViewData["Title"] = $"Page {page}";
            return View("Views/_Host.cshtml");         
        }
    }
}

_Layout.cshtml

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

@namespace BlazorSample.Views
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))

App.razor

@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

@inherits LayoutComponentBase
@Body

DisplayGridPage.razor

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.
<div id="sheet_area">
</div>

CSS isolation

先のリンクでも説明されていますが、CSS isolation は各 .razor ごとに個別の CSS を作ることができる、というものです。

手順としては CSS ファイルとして {プロジェクト名}.styles.css を読み込むよう設定すること、(先ほどの DisplayGridPage.razor であれば) DisplayGridPage.razor.css という CSS ファイルを用意する、という2点です。

_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <base href="~/" />
    <!-- Blazorが読み込まれるときに個別の CSS が渡される -->
    <link href="BlazorSample.styles.css" rel="stylesheet" />
</head>
<body>
    @RenderBody()
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
  • ドキュメントなどでは Host.cshtml で設定することになっていますが、 Layout.cshtml がある場合はこちらに書いても問題ありませんでした。

DisplayGridPage.razor.css

#sheet_area
{
    height: 30vh;
    width: 30vw;
    background-color: aqua;
    display: grid;
}
h1{
    color: blue;
}

失敗

これで実行すれば CSS が割当たり・・・・ませんorz

Controller クラスで「localhost:5000」と「localhost:5000/{page}」をルーティングして View を返しているため、「localhost:5000/BlazorSample.styles.css」までルーティングされていたという。。。


※2020-12-21 更新

Start.cs の Configure 内で、「app.UseStaticFiles();」より先に「app.UseRouting();」を実行してしまっていたのが原因でした。

検証までしていただいた @jsakamoto さん、ありがとうございます(..)_

何となくで書いてしまっていたところなので、勉強になりました :)


HomeController.cs

...
namespace BlazorSample.Controllers
{
    public class HomeController: Controller
    {
        [Route("")]
        [Route("Pages/{page}")]
        public ActionResult OpenPage(string page)
...
  • Middlewareで何とかする、という方法もありそうですが、可能であればパスを変えるのがシンプルな気はします。

わざわざ書くほどのことでもないのですが、他にも起こっていたはずの問題がなぜか再現しなかったため、せめてこれだけは残しておくことにします。

生成される CSS

「BlazorSample.styles.css」として渡される CSS は MainLayout.razor.css など親要素がある場合はそれらをマージしたものとなります。

BlazorSample.styles.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;
}

PostCSS で Autoprefixer を使ったときのようにベンダープレフィックスをつけたりしてくれるわけではないので、(IE とか IE とか IE とか)必要な場合は(今回の場合) Views に PostCSS から生成した CSS を出力する、というのが良さそうです。

なお、 .razor と .razor.css ファイルは同一階層にある必要があります。

Partial クラス (.razor.cs) と合わせて一か所に置くのが良いですね。