列挙体メンバーを文字列としてDBに保存

Entity Frameworkで列挙体をメンバーとして持つEntityをコードファーストにて、テーブルを作成すると、列挙体部分は整数(int)型となる。列挙体中の並びに変更が無ければ問題にならないのだが、DBに値を保存した後で列挙体の順番が変更されてしまうと齟齬が発生する。

列挙体への項目挿入などの変更が考えられる場合には、DB中に列挙体メンバー名を文字列化して保存した方が良いかもしれない。

このような値変換を行うには、DbContextから派生させたクラスで、OnModelCreatingをオーバーライドして、Entity中のプロパティについて値コンバーターを定義すればよい。

// Alert Level
public enum AlertLevel { Debug, Information, Warning, Error, Fatal }
// Message
public class AlertMessage {
    [Key]
    public int MessaegId { get; set; }
    public AlertLevel Level { get; set; }
    public string Message { get; set; } = null!;
    public DateTime LogDate { get; set; }
}
// DbContext
public class ConvTestCtx : DbContext {
    public DbSet<AlertMessage> Messages { get; set; } = null!;
		・・・
    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder
            .Entity<AlertMessage>()
            .Property(e => e.Level)
            .HasConversion(
                v=>v.ToString(),    // Instance⇒DB
                v=>(AlertLevel)Enum.Parse(typeof(AlertLevel),v) // DB⇒Instance
            );
    }
}

これで、生成されるテーブル作成SQLは以下の通り。

CREATE TABLE [Messages] (
    [MessaegId] int NOT NULL IDENTITY,
    [Level] nvarchar(max) NOT NULL,
    [Message] nvarchar(max) NOT NULL,
    [LogDate] datetime2 NOT NULL,
    CONSTRAINT [PK_Messages] PRIMARY KEY ([MessaegId])
);

ご覧のとおり、Levelがnvarchar(max)となっている。実際に登録した結果は以下の通り。

MessaegId Level	       Message 	           LogDate
5	  Information  System has Started  2023/03/24 08:53:15
6	  Warning      Low Battery	   2023/03/24 08:53:17
7	  Debug	       Debug Message	   2023/03/24 08:53:18

てな感じで、双方向の型変換が可能。

カテゴリー: .NET, Entity Framework, 技術系 | コメントする

RazorページでBlazorコンポーネントを使用する

Blazorプロジェクト中のRazorページ(.cshtml)等でBlazorコンポーネントを使用したい場合には、どうすればよいのかチョット気になったので調べてみた。

結果としてはTagHelperライブラリの<component>タグを用いるらしい。
詳細はこちらを参照。

componentタグにはレンダリングするTypeとレンダリングの方法を指定する。
(パラメータも指定可能)

おなじみのCounterコンポーネントを使ってみる

@page
@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers
@using blazorApp7.Pages

<html>
    <head>
        <title>Razorコンポーネントテスト</title>
        <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
        <link rel="stylesheet" href="~/css/site.css" />
        <script type="text/javascript" src="~/scripts/project.js"></script>
    </head>
    <body>
        <div class="areabody">
        <h3>CSHTMLからBlazorコンポーネントは使えるのか?</h3><br/>
            <div class="component-area">
                <component type="typeof(Counter)" render-mode="ServerPrerendered"/>
            </div>
        </div>
        <br/>
        <hr/>
        <div class="areabody">
            結論から言うと、Blazor用のJavaScriptおよびその他コンポーネント実行に必要なJavascriptとCSSを読み込めば大丈夫な模様。<br/>
            BlazorServerの場合は"_framework/blazor.server.js"<br/>
            <script src="_framework/blazor.server.js"></script>
        </div>
    </body>
</html>
実行例

Blazorサーバーの場合はサーバーとの通信がNGになると、ページ全体がダメになるのは仕方ないよね・・・

サーバー止めた

用途として、動的View的なものに使えないかと思ったけど、Razorページからコンポーネントへのリアルタイム通知ができないとダメだね。無理やり作ろうと思えばできないことは無いけど、やはり単純にBlazor内で完結させた方がよさそうだ。

カテゴリー: .NET, asp.net core, Blazor, C#, 技術系 | コメントする

List.ContainsやIndexOfの比較を値で行う

List<T>などでTが参照型の場合、ContainsやIndexOfは値(オブジェクト内容)ではなく、参照先を判定する仕様となっている。

これを、値(オブジェクト内容)で判定させたい場合はどうするのかというと、T型にIEquatable<T>インターフェースをインプリメントして、Equalsメソッドを定義して上げれば良い。

// ノーマルバージョン
public class Person {
	public string Name { get; set; }
	public string Mail { get; set; }
}
var p = new Person() { Name = "T.Sumomo", Mail = "t.sumomo@momo.com" };
var p1 = new Person() { Name = "T.Sumomo", Mail = "t.sumomo@momo.com" };
p == p1
p.Equals(p1)
List<Person> l1 = new List<Person>() { p };
l1.Contains(p1)
l1.IndexOf(p1)
// 値比較バージョン
public class Person1 : IEquatable<Person1> {
	public string Name { get; set; }
	public string Mail { get; set; }
	// 値で比較
	public bool Equals(Person1 other) {
		return other?.Name == Name && other?.Mail == Mail;
	}
}
var p2 = new Person1() { Name = "T.Sumomo", Mail = "t.sumomo@momo.com" };
var p3 = new Person1() { Name = "T.Sumomo", Mail = "t.sumomo@momo.com" };
p2 == p3
p2.Equals(p3)
List<Person1> l2 = new List<Person1>() { p2 };
l2.Contains(p3)
l2.IndexOf(p3)
> p == p1
false
> p.Equals(p1)
false
> l1.Contains(p1)
false
> l1.IndexOf(p1)
-1
>
> p2 == p3
false
> p2.Equals(p3)
true
> l2.Contains(p3)
true
> l2.IndexOf(p3)
0

と、このように値で判定を行う事ができる。

もちろん、内容全部を比較するのでは無く、キーとなるプロパティだけの比較とかでも問題は無い。
ちなみに、Equalsではなく、== operatorをEqualsと同じ内容にしても、ContainsやIndexOfは参照先の比較になってしまう。

まぁ、わざわざクラスに手を入れないで、LINQ使ってもいいのだけど・・・

カテゴリー: .NET, C#, 技術系 | コメントする

blazor Dynamic Component

.NET6から、blazorのタグとして、<DynamicComponent>がサポートされた。

このタグはrazorコンポーネントを動的に指定して表示するものであり、ページ内で、表示するコンポーネントの切替などを行う事を想定していると思われる。

使い方は以下のような感じ

@page "/"

<PageTitle>Dynamic Component Test</PageTitle>
<br/>
<p>
    <DynamicComponent>のテスト<br/>
    下記からコンポーネントを選んで、そのコンポーネントを表示します。
</p>
コンポーネント:
<select @onchange="OnDropDownChange">
    <option value=""> </option>
    <option value="@nameof(Counter)">Counter</option>
    <option value="@nameof(FetchData)">Fetch Data</option>
</select><br/>
<hr/>
<div class="ComponentBody">
@if (ComponentType != null) {
    <DynamicComponent Type="@ComponentType" Parameters="@Params" />
}
</div>
@code {

    protected Type? ComponentType = null;
    protected IDictionary<string,object>? Params;

    protected void OnDropDownChange(ChangeEventArgs e) {
        if (e.Value?.ToString()?.Length != 0) {
            ComponentType = Type.GetType($"BlDynamic.Pages.{e.Value}");
            if (e.Value?.ToString() == nameof(Counter)) {
                Params = new Dictionary<string,object>() { {"InitialCount",5} };
            } else {
                Params = null;
            }
        } else {
            ComponentType = null;
        }
    }

}

表示するコンポーネントをselectタグで選んで、そのコンポーネントを表示する簡単な例となっている。使用しているコンポーネントはblazorプロジェクトを作成すると、自動的に生成される、Counter.razorとFetchData.razor。

Counterの方にはInitialCountと言うパラメータを追加して、この呼出し例では5を設定している。

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Parameter]
    public int InitialCount { get; set; } = 0;
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        base.OnInitialized();
        currentCount = InitialCount;
    }
    
    private void IncrementCount()
    {
        currentCount++;
    }
}
実行例1
実行例2
実行例3

このように、動的に表示するコンポーネントを変更することが可能。

何かに使えるかもしれないので、メモ。

カテゴリー: .NET, asp.net core, Blazor, C#, 技術系 | コメントする

Blazor Hybrid-コンポーネントとホスト間I/F

以前、Blazor Hybridについて簡単に説明したが、razorコンポーネントとホスト間のやりとりについて、もう少し詳しく説明してみる。(記事のコメントにも少々文書で書いたが、今回は簡単な実例で説明してみる。)

まず、ホスト,コンポーネント双方で使用する、インターフェースとその実装を以下のような感じで作成する。

public delegate void OnClientEvent(object sender, EventArgs e);
public interface IHostInterface {
    public string Message { get; set; }
    public int Count { get; set; }
    /// <summary>
    /// コンポーネントのリフレッシュ等を行なうメソッド
    /// (コンポーネント側でセットする)
    /// </summary>
    /// <value></value>
    public Action ComponentRefresh { get; set; }
    /// <summary>
    /// コンポーネントでイベントが発生した時に呼ばれるホスト側イベントハンドラ
    /// </summary>
    public event OnClientEvent ComponentEvent;
    /// <summary>
    /// コンポーネント側からイベントを発生させるメソッド
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public void ExecClientEvent(object sender, EventArgs e);
}
public class HostInterface : IHostInterface {
    public string Message { get; set; } = null!;
    public int Count { get; set; } = 0;
    public Action ComponentRefresh { get; set; } = null!;
    public event OnClientEvent ComponentEvent = null!;
    public void ExecClientEvent(object sender, EventArgs e) {
        ComponentEvent.Invoke(sender,e);
    }
}

コンポーネント側では、このインターフェースを@injectでSingletonオブジェクトとして、使用できるようにする。

下記の例では、インターフェイス中のCounterプロパティをホストとの共有データとして使用し、ホスト側のトリガで、コンポーネントの更新が行えるよう、ComponentRefreshでStateHasChangedメソッドを呼び出せるようにしている。

また、コンポーネント側のイベントを、ホストにも知らせることができるよう、イベント発生時に、ホスト側のイベントハンドラを呼び出している。

@inject IHostInterface HostIF

<h1>Counter</h1>

<p>Current count: @HostIF.Count</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    protected override void OnInitialized() {
        // Componentの再描画メソッドを設定
        HostIF.ComponentRefresh = (Action)(()=>StateHasChanged());
    } 

    private void IncrementCount() {
        // カウントアップした後、ホスト側へイベント通知
        HostIF.Count++;
        HostIF.ExecClientEvent(this,new EventArgs());
    }
}

ホスト側では、コンストラクタで、ComponentEventにハンドラを設定することにより、コンポーネントで状態変更が有った場合の処理を定義できる。

private HostInterface HostIF;
public Form1()
{
    InitializeComponent();
    var services = new ServiceCollection();
    services.AddWindowsFormsBlazorWebView();
    // Singletonオブジェクトの作成
    HostIF = new HostInterface();
    // コンポーネントから呼び出されるイベントハンドラ
    HostIF.ComponentEvent += On_ClientEvent;
    // Singletonオブジェクトをコンポーネントサービスに追加
    services.AddSingleton<IHostInterface>(HostIF);
    blView.HostPage = @"wwwroot\index.html";
    blView.Services = services.BuildServiceProvider();
    blView.RootComponents.Add<Counter>("#app");
}
/// <summary>
/// インクリメントボタンクリックイベントハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void On_btnIncrement_Click(object sender, EventArgs e) {
    // Singletonのカウンタをインクリメント
    HostIF.Count++;
    lblMessage.Text = $"カウント:{HostIF.Count}";
    // コンポーネント表示を更新
    HostIF.ComponentRefresh();
}
/// <summary>
/// コンポーネントイベントハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void On_ClientEvent(object sender, EventArgs e) {
    // ホストの表示を更新
    lblMessage.Text = $"カウント:{HostIF.Count}";
}

実行すると以下のように、ホストとコンポーネント間の同期が取れていることが分かる。

何かに使えるかも知れないので、メモ。

カテゴリー: .NET, Blazor, C#, 技術系 | コメントする

Microsoft Graphでアカウントパスワード変更

Office365のアカウントパスワードを忘れる社員が多いので、なんとか自動化したいなと思い、以下のようなフローでパスワードリセットを行なう仕組みを考えた。

Microsoft Graphを使用してユーザーのパスワードを強制的に再設定するには、下記のように、PasswordProfileを使用する必要がある。Microsoft Graph APIの.ChangePassword()メソッドを使用するには、旧パスワードが必須なため旧パスワードが分からないと、変更できないので・・・

using Microsoft.Graph;
using Azure.Identity;
・・・
var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var cred = new UsernamePasswordCredential(AdminUsername,AdminPassword,TenantId,ServiceId,options);

// scopeにはUser.ReadWrite.All,Directory.ReadWrite.Allが必要
var graphClient = new GraphServiceClient(cred,scopes);

// テンポラリパスワードの生成
string newPassword = GeneratePassword();
// パスワードの変更
try {
    User user = new User();
    user.PasswordProfile = new PasswordProfile() {
        ForceChangePasswordNextSignIn = true,           // 次のログイン時にパスワード強制変更
        ForceChangePasswordNextSignInWithMfa = false,
        Password = newPassword                          // テンポラリパスワード
    };
    // パスワード情報の更新
    await graphClient.Users[targetaccount]
        .Request()
        .UpdateAsync(user);
} catch (Exception e) {
    Message = $"パスワードリセットに失敗しました。<br/>{e.Message}";
    return;
}

ちなみに、HTTPメソッドはUpdateAsync()=PATCHを使用する。

備忘録として。

カテゴリー: .NET, C#, Microsoft Graph, 技術系 | 1件のコメント

bootstrapモーダルダイアログとblazor

bootstrapでは、標準でモーダルダイアログをサポートしている。以下のような感じで、divタグへ特定のCSSクラス名を指定するだけで、簡単にモーダルダイアログを表示させることができる。

<button class="btn btn-primary" data-toggle="modal" data-target="#Modal">ユーザー選択</button>
<br/>
<span style="color:red;font-weight:bold">@mesg</span>
<!-- モーダル本体 -->
<div class="modal fade" id="Modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
	<div class="modal-dialog modal-dialog-centered" role="document">
		<div class="modal-content">
			<!-- モーダルヘッダ部 -->
			<div class="modal-header" style="background-color:navy;color:white">
				<h5 class="modal-title" id="exampleModalLongTitle">Modal Test</h5>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
				  <span aria-hidden="true">×</span>
				</button>
			</div>
			<!-- モーダルボディ -->
			<div class="modal-body" style="height:500px">
			@if (Employees != null) {
				<select size="20" style="width:200px" @bind="selectedEmpNo">
					@foreach(var itm in Employees) {
					  <option value="@itm.EmpNo">@itm.Name</option>
					}
				</select>
			}
			</div>
			<!-- モーダルヘッダ -->
			<div class="modal-footer">
				<button type="button" class="btn btn-secondary" @onclick="OnCancelClick">キャンセル</button>
				<button type="button" class="btn btn-primary" @onclick="OnOKClick">選択</button>
			</div>
		</div>
	</div>
</div>

blazorでも以下のscriptを読込んでおけば、使用できる。

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>

モーダルのクローズをJavascriptから明示的に行なうには、modal(‘hide’)を使用する。

var modalope = {
    close: (target) => {
        var eleid = '#' + target;
        $(eleid).modal('hide');
    },
    open: (target) => {
        var eleid = '#' + target;
        $(eleid).modal();
    }
};

以下はblazorのcode部分

@code {
	protected string mesg = "";
	protected string selectedEmpNo = "";
	protected List<Employee> Employees = null!;
		・・・
	protected async Task OnOKClick() {
		await JS.InvokeVoidAsync("modalope.close","Modal");
		var emp = Employees.Where(v=>v.EmpNo == selectedEmpNo).FirstOrDefault();
		if (emp != null) {
		  mesg = $"「{emp.EmpNo}:{emp.Name}」が選択されたよ!!";
		}
	}
	protected async Task OnCancelClick() {
		await JS.InvokeVoidAsync("modalope.close","Modal");
		mesg = "選択がキャンセルされたよ!!";
	}
	・・・
}

実行結果はこんな感じ

実行画面1
実行画面2

もちろん、divのidを変えれば、複数のダイアログを使用することが可能。

結構便利に使えそう。

カテゴリー: .NET, asp.net core, Blazor, C#, 技術系 | コメントする

ZipArchiveによる、ZIPファイルの作成

ZipファイルにアクセスするためのライブラリとしてはDotNetZipが有名だが、.NET標準のSystem.IO.Compression.ZipArchiveクラスを使用した、ZIPファイルの作成についての備忘録を書いてみようと思う。

なぜ、ZipArchiveを利用しようとしたのかというと、最初はDotNetZipを使ってコードを書いたのだが、Webページからのダウンロードがうまく動作せず、原因を突き止めるより、別のライブラリを使用した方が早いかなという感じで、色々調べてみたのだが、まぁ、.NET標準だし、これでいいかなと採用したわけ。

ZIPファイルを作成するには、ZipFileクラスを使った方が簡単なのだが、今回はWebアプリで、動的に作成した内容をZip化したいので、ZipArchiveクラスを使用する。

ZipArchiveクラスは書込先となるStreamを指定して作成する。その中にエントリを追加して行く形だ。

実際のコードを見てみると以下のような感じ。

using System.IO;
using System.IO.Compression;
・・・
using(MemoryStream zipstream = new MemoryStream()) {	// ZipArchiveを書込むStream
	using(ZipArchive ar = new ZipArchive(zipstream,ZipArchiveMode.Create,true)) {	// ZipArchiveの作成
		// ZipEntry=ファイルの作成
		ZipArchiveEntry ent = ar.CreateEntry(entry1);
		using(Stream stm = ent.Open()) { // エントリのStreamを取得
			using(StreamWriter wtr = new StreamWriter(stm,Encoding.GetEncoding("shift_jis"))) {	// 文字列書き込み用StreamWriter
				// ファイル内容作成
				・・・
				await wtr.FlushAsync();
				await stm.FlushAsync();
			}
		}
		// 必要分上記の繰返し
		・・・
	}
	await zipstream.FlushAsync();
	byte[] buff = zipstream.ToArray();
	return File(buff,"application/octet-stream",ZipFileName);
}

DotNetZipと比べると多少面倒で、パスワードがけられない等の問題もあるけど、目的が果たせたから良しとしよう。

カテゴリー: .NET, C#, 技術系 | コメントする

勘違い(MultipartFormDataContent+WebAPI)

メール送信を行なう、WebAPIを作成しようとして、以下のようなインターフェイスを考えた。

[HttpPost]
public async Task<SendMailStatus> PostAsync(
    [FromForm]string? FromAddress,		// 送信者アドレス
    [FromForm]string? Password,			// (暗号化された)パスワード
    [FromForm]string[]? ToAddresses,	// 送信先(複数指定可)
    [FromForm]string[]? CcAddresses,	// Cc(複数指定可)
    [FromForm]string[]? BccAddresses,	// Bcc(複数指定可)
    [FromForm]string? Subject,			// 件名
    [FromForm]string? Message,			// 本文
    [FromForm]IFormFile[]? Attachments	// 添付ファイル(複数指定可)
) {
	・・・

添付ファイルがあるので、当然、multipart/form-dataとして、クライアントからデータをPOSTしなければならないのだが、この作成方法に引っかかってしまったのであった。

MultipartFormDataContent構造体を使用するのは分かっており、これにファイルを追加するにはBinaryContent構造体を使用すれば良いことも分かっていたのだが、それ以外のFormデータを「Formデータだから、FormUrlEncodedContent構造体を作って、それを追加すればいいんじゃ無いか?」と勝手に思い込んで、色々試したが、ファイル(コード上はAttachments)は取れるのだが、他の引数はうまくデータがとれず、「何じゃこれは?」となった・・・

色々調べて、結局FormUrlEncodedContentでまとめて入れるのでは無く、以下のように個々のパラメータ(key=値)をStringContentとして別々に追加しなければいけないことが分かった。

// マルチパートMime
MultipartFormDataContent MimeData = new MultipartFormDataContent();

// 送信元
StringContent ctx = new(FromAddress);
MimeData.Add(ctx,"FromAddress");

// パスワード
string encPwd = EncryptPassword(Password));
ctx = new(encPwd);
MimeData.Add(ctx,"Password");

// 宛先
foreach(string s in ToAddresses) {
    if (s != null) {
        ctx = new(s);
        MimeData.Add(ctx,"ToAddresses");
    }
}
	・・・
// 添付ファイル
foreach(var a in Attachments) {
    MemoryStream stm = new MemoryStream();
    await a.CopyToAsync(stm);
    await stm.FlushAsync();
    byte[] buf = stm.ToArray();
    ByteArrayContent c = new ByteArrayContent(buf);
    MimeData.Add(c,"Attachments",a.FileName);
}
await cli.PostAsync("・・・",MimeData);

まったく、基礎がなっておりませぬな・・・

無駄な時間を使ってしまった・・・

カテゴリー: .NET, asp.net core, C#, Web, 技術系 | コメントする

.NET7のJSONシリアライザ

.NET7になって、JSONシリアライザがアップデートされ、今までサポートしていなかったDateOnly型やTimeOnly形の変換を行ってくれるようになった。

.NET6と.NET7を比べると以下のよう感じ。(dotnet-scriptで実行)

using System.Text.Json;
public class Person {
  public string Name { get; set; }
  public DateOnly Birthday { get; set; }
}
List<Person> plst = new();
Person p = new() { Name="T.Sumomo", Birthday=new DateOnly(1964,2,3)};
plst.Add(p);
p = new() { Name="J.Sumomo", Birthday=new DateOnly(1988,5,13)};
plst.Add(p);
string json = JsonSerializer.Serialize(plst);
json

結果(.NET6)

System.NotSupportedException: System.NotSupportedException: Serialization and deserialization of 'System.DateOnly' instances are not supported. Path: $.Birthday.
  + System.Text.Json.ThrowHelper.ThrowNotSupportedException(ref System.Text.Json.WriteStack, System.NotSupportedException)
  + JsonConverter<T>.WriteCore(System.Text.Json.Utf8JsonWriter, ref T, System.Text.Json.JsonSerializerOptions, ref System.Text.Json.WriteStack)
  + System.Text.Json.JsonSerializer.WriteUsingSerializer<TValue>(System.Text.Json.Utf8JsonWriter, ref TValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo)
  + System.Text.Json.JsonSerializer.WriteStringUsingSerializer<TValue>(ref TValue, System.Text.Json.Serialization.Metadata.JsonTypeInfo)
  + System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, System.Text.Json.JsonSerializerOptions)

DateOnlyのシリアライズ・デシリアライズをサポートしていないので、エラー

結果(.NET7)

"[{\"Name\":\"T.Sumomo\",\"Birthday\":\"1964-02-03\"},{\"Name\":\"J.Sumomo\",\"Birthday\":\"1988-11-23\"}]"

DateOnlyのシリアライズ・デシリアライズをサポートしたので、正常にJSONにシリアライズされる。

もちろん、デシリアライズもOK

var xlst = JsonSerializer.Deserialize<List<Person>>(json);
xlst

結果

List<Submission#1.Person>(2) { Submission#1.Person { Birthday=[1964/02/03], Name="T.Sumomo" }, Submission#1.Person { Birthday=[1988/11/23], Name="J.Sumomo" } }

大した変更ではないが、結構ありがたい。

カテゴリー: .NET, C#, 技術系 | コメントする