WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

ASP.NET Core MVC で三点リーダー表示

by WebSurfer 19. May 2024 13:21

ASP.NET Core MVC で表示するテーブルで css の overflow: hidden と text-overflow: ellipsis を使って文字列の長さ制限するとともに末尾に三点リーダーを表示する例を紹介します。下の画像がその例です。ターゲットフレームワークは .NET 8.0、ブラウザは Chrome、フォントはメイリオ、サイズは 16px です。

文字列の長さ制限、三点リーダー表示

先の記事「文字列の長さ制限、三点リーダー表示」では、ASP.NET Web Forms アプリの GridView の例を紹介しました。

その記事と同様に、description 列の文字列を div 要素に入れて、それに以下の css を適用しています。

<style type="text/css">
    div.style1
    {
        width: 320px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
</style>

html と css の機能なのでアプリが ASP.NET Web Forms から ASP.NET Core MVC に変わっても関係ないと思われるかもしれませんが、ブラウザに送信される文字の形式が異なります。結果は同じになりますが。

参考までに以下に違いを書いておきます。

先の記事の Web Forms の例では、コードビハインドで C# のコードで設定した文字列は UTF-8 に変換されてブラウザに送信されています。文字列は Literal コントロールの Text プロパティに設定したのでエスケープはされません。(なので、先の記事では < > & という文字は C# のコードでは &lt; &gt; &amp; としています)

一方、この記事の ASP.NET Core MVC の場合は、< > & という文字は ASP.NET がエスケープして &lt; &gt; &amp; という文字列としてブラウザに送信されます。また、日本語や絵文字(たぶん非 ASCII 文字すべて)は数値文字参照 &#xNNNN; 形式(NNNN は 16 進 Unicode)に変換されてブラウザに送信されます。以下の画像を見てください。

html ソース

ちなみに、例えば、🍎 は上の画像では &#x1F34E; に該当します。🍎 を IME パッドで見ると Unicode: U+1F34E となっています。

🍎 のコード

一番上の画像の結果から分かるように、サーバーからブラウザに送信される各文字の形式は関係なく、ブラウザ上に表示された文字列の長さで制限がかかり、css の width: 320px で指定された幅いっぱいに三点リーダを含めて表示されています。

フォントはメイリオ、サイズは 16px ですが、MS Gothic などの等幅フォントを使った場合も、フォントサイズを変えた場合も、ブラウザ上に表示される文字列の長さで制限がかかるのは同じです。

三点リーダーを表示する text-overflow:ellipsis はもともと IE の独自拡張だそうですが、最近は他のブラウザでも取り入れられているようです。Windows 10 の Chrome 125.0.6422.61, Edge 125.0.2535.51, Firefox 126.0, Opera 110.0.5130.23 で試してみましたが、同じ結果が得られました。

参考に、上の画像を表示するのに使った ASP.NET Core MVC アプリのコードを載せておきます。

Model

namespace MvcNet8App2.Models
{
    public class Ellipsis
    {
        public int Id { get; set; }
        public string Description { get; set; } = null!;
    }
}

View

@model IEnumerable<MvcNet8App2.Models.Ellipsis>

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

<style type="text/css">
    body {
        font-family: "メイリオ";
        font-size: 16px;
    }

    div.style1 {
        width: 320px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
</style>

<h1>Ellipsis</h1>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Id)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Description)
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Id)
                </td>
                <td>
                    <div class="style1">
                        @Html.DisplayFor(modelItem => item.Description)
                    </div>
                </td>                
            </tr>
        }
    </tbody>
</table>

Controller / Action Method

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MvcNet8App2.Models;
using System.Diagnostics;
using System.Security.Claims;
using System.Security.Principal;

namespace MvcNet8App2.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly CookieAuthenticationOptions _options;

        public HomeController(ILogger<HomeController> logger,
                              IOptions<CookieAuthenticationOptions> options)
        {
            _logger = logger;
            _options = options.Value;
        }


        // ・・・中略・・・

        public IActionResult Ellipsis()
        {
            var model = new List<Ellipsis>
            {
                new ()
                {
                    Id = 1,
                    Description = "エスケープされた < > & " +
                    "などの文字はどのようになるか?"
                },
                new() {
                    Id = 2,
                    Description = "Proportional Font WWWWWWWWWWW " +
                    "iiiiiiii llllll などは?"
                },
                new ()
                {
                    Id = 3,
                    Description = "サロゲートペア 𠀋 𡈽 𠮟 などは" +
                    "どのようになるか?"
                },
                new() {
                    Id = 4,
                    Description = "絵文字 🍎 🍏 (サロゲートペア) " +
                    "👨‍🌾 (ZWJ で結合) などは?"
                }
            };

            return View(model);
        }

        // ・・・中略・・・
    }
}

Tags: , , , , ,

CORE

Razor クラスライブラリ

by WebSurfer 20. March 2024 15:43

Razor Class Library (RCL) に ASP.NET Core MVC の Controller と View を実装し、それを ASP.NET Core MVC アプリから利用する例を書きます。

Razor クラスライブラリの利用

話の発端は Microsoft Q&A のスレッド「.NET MVCで参照登録したDLLのビューを使用する方法」でクラスライブラリ中のビューが見つからないという話です。それに対処する方法を検討した時の話を備忘録として残しておくことにしました。

ASP.NET Core MVC の cshtml ファイルは、Microsoft のドキュメント「ASP.NET Core での Razor ファイルのコンパイル」によるとデフォルトでビルド時及び��プロイ時に dll にコンパイルされるそうなので、.NET Framework 版の MVC のようなランタイムコンパイルだからダメということはなさそうです。

ただし、アクションがビューを返すときにビューの検出と呼ばれるプロセスが行われるそうで、Microsoft のドキュメント「ビューの検出ルール」に書いてあるように、既定のフォルダに当該ビューが存在しなければなりません。

Microsoft Q&A の話は、独立したクラスライブラリに MVC の Controller と View を実装した結果、MVC アプリがビューを見つけることができず、InvalidOperationException: The view 'Index' was not found いう例外がスローされたということです。

クラスライブラリではなくて、ASP.NET Core MVC 本体のプロジェクトに Controller, Model, View を実装すればそういう悩みは解消できますが、どうしても独立したライブラリを使わなければならない事情があるなら、Razor Class Library として作るのが良さそうです。

Razor Class Library は Razor Pages だけでなく MVC もサポートしており、設定によって Controller, View, Model を実装して、MVC アプリからそれらを呼び出すことができるそうです。

ということで、Microsoft のドキュメント「ASP.NET Core の Razor クラス ライブラリ プロジェクトを使用した再利用可能 UI の作成」とネットで見つけた記事「Working with Razor Class Libraries in ASP.NET Core」を参考にして作ってみました。

ソリューションの構成

上の画像の RazorClassLib が RCL プロジェクトで、RazorClassLibraryHost が MVC アプリのプロジェクトです。両方とも Visual Studio 2022 のテンプレートで作って、前者には Controllers, Models, Views フォルダを追加し、それらの中に Controller, Model, View クラスを実装しています。後者はテンプレートで作ったまま何も手を加えてません。

MVC アプリの実行結果がこの記事の一番上の画像で、MVC アプリから RCL の Controller / View を呼び出して結果を表示したものです。ビューが見つからないという問題は起こりません。

何も難しいことは無かったのですが、Visual Studio で RCL プロジェクトを作成する際に注意すべき点があって、それは[サポート ページとビュー]にチェックを入れることです。

[サポート ページとビュー]にチェック

上に紹介した Microsoft のドキュメントに "Select Support pages and views if you need to support views. By default, only Razor Pages are supported." と書いてありますが、デフォルト([サポート ページとビュー]にチェック無し)では Razor Pages も MVC もサポートされません。その下に書いてある "The Razor class library (RCL) template defaults to Razor component development by default. The Support pages and views option supports pages and views." が正しいです。

Microsoft のドキュメント「クラス ライブラリで ASP.NET Core API を使用する」の「MVC 拡張機能を含める」のセクションの説明によると以下の設定が必要とのことです。

  1. Microsoft.NET.Sdk.Razor SDK を使用する。
  2. true に設定された AddRazorSupportForMvc MSBuild プロパティ。
  3. 共有フレームワークの <FrameworkReference> 要素。

Visual Studio 2022 の RCL テンプレートで[サポート ページとビュー]にチェックを入れて作成したプロジェクトは上の要件を満たしていて、生成されるプロジェクトファイルの内容は以下のようになります。

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
  </PropertyGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
  
</Project>

その他、気が付いた点を以下に書いておきます。

MVC アプリから RCL に参照設定がされていれば、RCL の Controller は、MVC アプリのプロジェクトの Program.cs に含まれる builder.Services.AddControllersWithViews(); で DI コンテナに登録されるようです。なので、参照設定以外は何もしなくても、要求に応じて RCL の Controller が呼び出され応答が返ってきます。

MVC アプリから RCL の Razor Pages を呼び出す場合は、Program.cs に builder.Services.AddRazorPages(); と app.MapRazorPages(); を追加する必要があります。追加しないと見つからないというエラーになります。

MVC アプリに実装されている _Layout.cshtml が自動的に使われます。なので、MVC アプリの wwwroot に実装されている Bootstrap などの CSS や jQuery などの JavaScript も有効になります。

Tags: , , , ,

CORE

参照型と ASP.NET Core の検証

by WebSurfer 23. February 2024 17:49

.NET 6.0 以降の ASP.NET Core Web アプリで、コンテキストクラスとエンティティクラスを使って SQL Server などのデータベースのテーブルの CRUD 操作を行う際、エンティティクラス内の参照型のプロパティを null 許容にしておかないと検証に通らず、Create や Edit 操作に失敗するという話を書きます。特に下の画像のようなナビゲーションプロパティが要注意です。

検証結果が NG

また同じ問題にハマって時間を無駄にしないよう、どういうことかを書いておくことにしました。

先の記事「null 許容参照型と EF Core Code First」で書きましたように、Visual Studio 2022 のテンプレートを使って、ターゲットフレームワークを .NET 6.0 以降の設定にしてプロジェクトを作ると、デフォルトで「Null 許容」オプションが有効になり、Code First の場合は生成されるデータベースのフィールドの NULL 可/不可が、リバースエンジニアリングでデータベースからエンティティクラスを生成する場合はプロパティの型の null 許容/非許容が影響を受けます。

エンティティクラスをビューモデルに使ってブラウザからのデータを MVC アプリのアクションメソッドで受け取る場合、null 許容参照型に注意が必要です。特にナビゲーションプロパティの型による問題は気が付きにくいと思いました。

具体的にどういうことかを下の画像の SQL Server の Blogging データベースの dbo.Blogs, dbo.Posts テーブルを例に使って説明します。

SQL Server データベース

上の dbo.Blogs, dbo.Posts テーブルからリバースエンジニアリングでコンテキストクラスとエンティティクラスを生成します。ターゲットフレームワークは .NET 8.0 で「Null 許容」オプションはプロジェクト全体で有効に設定された状態です。パッケージマネージャコンソールから発行したコマンドを参考に下に載せておきます。

Scaffold-DbContext -Connection "接続文字列" -Provider Microsoft.EntityFrameworkCore.SqlServer -ContextDir Data -OutputDir Models -Tables Posts, Blogs -DataAnnotations

その結果、以下のエンティティクラスが生成されます。(同時にコンテキストクラスも生成されますが省略)

Blog クラス

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

namespace RazorPages1.Models;

public partial class Blog
{
    [Key]
    public int BlogId { get; set; }

    public string Name { get; set; } = null!;

    [InverseProperty("Blog")]
    public virtual ICollection<Post> Posts { get; set; } = 
        new List<Post>();
}

Post クラス

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace RazorPages1.Models;

[Index("BlogId", Name = "IX_Posts_BlogId")]
public partial class Post
{
    [Key]
    public int PostId { get; set; }

    public string Title { get; set; } = null!;

    public string? Content { get; set; }

    public int BlogId { get; set; }
    
    [ForeignKey("BlogId")]
    [InverseProperty("Posts")]
    public virtual Blog Blog { get; set; } = null!;   
}

SQL Server の dbo.Blog テーブルの外部キーフィールド BlogId が NULL 不可になっているため、Blog クラスの Posts ナビゲーションプロパティと、Post クラスの Blog ナビゲーションプロパティの型は Null 許容になりません。

上の Post クラスをベースに、Visual Studio 2022 のスキャフォールディング機能を使って dbo.Posts テーブルの CRUD を行う ASP.NET Core MVC アプリのコントローラーとビューを生成し、アプリを実行してレコードの Create 操作をしようとしたところ ModelState.IsValid が false(検証結果 NG)となって失敗したというのがこの記事の一番上の画像です。Create 操作だけでなく、Edit 操作でも同様に検証 NG で失敗します。

検証 NG となった原因は、Post クラスで null 非許容のナビゲーションプロパティ Bolg に、モデルバインディングの際に null が代入されたからです。

なぜ null が代入されるかですが、下のスキャフォールディングで自動生成された Create 画面を見てください。ブラウザから送信されてくるのは Title, Content, BlogId だけということに注目してください。

Create 画面

一方 Create アクションメソッドの引数は、この記事の一番上の画像の通り Post クラスになっています。ブラウザからデータが送信されてこない PostId, Blog プロパティにはモデルバインディングの際その型のデフォルト値(参照型は null)が代入されます。一番上のデバッグ画面のローカル変数の値を見てください。

Blog ナビゲーションプロパティの型は null 非許容なので、ModelState.IsValid が false になり、Create, Edit に失敗します。

解決策は、生成されたコードに手を加えて Null 許容にすることです。

// 以下のように null 許容にすれば検証は通る
public virtual Blog? Blog { get; set; }

ちなみに、PostId もブラウザからは送信されてきませんのでモデルバインディングでデフォルト値が代入されますが、int 型のデフォルト値は null ではなくて 0 (ゼロ) なので問題は出ません。一番上の画像のローカル変数の青枠を見てください。

また、Title も null 非許容ですが、こちらは検証結果のエラーメッセージがブラウザの画面上の当該テキストボックスの下に表示されるので、すぐ気が付きます。Blog の方はエラーメッセージは出ませんので気づき難いです。

Tags: , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  May 2024  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar