C#12の新機能(一部抜粋)

.NET8でサポートされる、C#12の新機能を調べてみた。

①コレクション式
コレクション式は配列やList等の初期化や代入などに使用できる。
以下のような感じ。

int[] iary_old = new int[] { 1, 2, 3, 4 };	// 従来
int[] iary_new = [ 1, 2, 3, 4 ];	// C#12以降

List<string> slst_old = new List<string>() { "Hello" , "World" };	// 従来
List<string> slst_new = [ "Hello" , "World" ];	// C#12以降

newと初期化を一緒に行うシンタックスシュガーだね。

また、スプレッド演算子”..”というものが使用できる。これを、コレクションの前につけると、コレクションの要素に置き換わる。

> int[] a = [1,2,3];
> int[] b = [4,5,6];
> int[] c = [..a,..b];
> c
int[6] { 1, 2, 3, 4, 5, 6 }

②ラムダ式パラメータのデフォルト値
ラムダ式のパラメータにデフォルト値を設定することができるようになった。

var PlusN = ( int Value, int Addition=1 ) => Value + Addition;

Console.WriteLine($"PlusN(10)={PlusN(10)}");
Console.WriteLine($"PlusN(10,2)={PlusN(10,2)}");
/*	結果
	PlusN(10)=11
	PlusN(10,2)=12
 */
// NGパターン
Func<int,int,int> PlusN = ( Value, Addition=1 ) => Value + Addition;
// error CS9098: 暗黙的に型指定されたラムダパラメーター 'Addition' に既定値を指定することはできません。
Func<int,int,int> PlusN = ( int Value, int Addition=1 ) => Value + Addition;
// warning CS9099: パラメーター 2 のラムダでの既定値は '1' だが、ターゲットデリゲート型では '<missing>' です。

パラメータの型を明示的に指定しないと、デフォルト値の設定は出来ないようだ。
また、型として引数の数を指定してしまうとコンパイル時に警告が出て、実行時にデフォルトパラメータを指定しないと、実行時エラー
CS7036: ‘arg2’ の必要なパラメーター ‘Func’ に対応する特定の引数がありません
となってしまう。

③プライマリコンストラクタ
record型では以前からサポートされていたが、classやstructでも使用できるようになった。

public class Person(string Name, string Mail, DateTime Birthday) {
	public string Name { get; } = Name;
	public string Mail { get; } = Mail;
	public DateTime Birthday { get; } = Birthday;
}

他にもいくつか新機能があるのだが、すぐに使えそうなのはこれぐらいかな。

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

Windows Serverをバージョンアップ後サービスが消えた件

Windows Server 2012 R2のEOSに伴い、2012 R2⇒2022にバージョンアップしたところ、一部のサービス登録が消えていた。

該当サーバーではあるプロダクトの一部として、Tomcatを使用しているのだが、OSをバージョンアップしたら、エラーとかは一切出ずに終了したのだが、何故かTomcatサービスの登録だけが消えていた。Tomcatで使用するJAVA_HOME環境変数も同じく。
(そのプロダクトの別サービスは消えていない)

ググってみたが、該当するような記事は見つからない。

とりあえず、JAVA_HOMEをシステム環境変数に登録し、Tomcatもサービス登録して、起動したら、問題無く動いているが、勝手にサービス登録を消されるのは困る・・・

せめて、事前チェックでサポートされていないとかの情報を出力して欲しいものである。

別のサーバーではMS製品しか使用していないので、特にこういう問題は起こっていなかったので、最初は何かと思ったよ。

ん?よくよく調べると、Windows Servre 2012 R2からWindows Server 2022へのインプレースアップグレードって、サポートされていないのか・・・

なら、SETUP実行時に警告かエラー出してくれよ・・・Orz

カテゴリー: 保守・運用 | コメントする

MailKit SMTP送信エラー

アプリケーションからメールを送信するために、以下のような感じでMailKit.Net.Smtpを使用している。

SmtpClient cli = new SmtpClient();
await cli.ConnectAsync("smtp.boo.foo",25);
await cli.SendAsync(msg);

Connectメソッドで、サーバーとポートのみ指定する形。今までは普通に動いていたのだが、SMTPサーバーをIISのSMTPからLinux sendmailに変更したところ、以下のような例外が発生した。

An error occurred while attempting to establish an SSL or TLS connection.
The host name (xxx.xxx.xxx) did not match the name given in the server's SSL certificate (yyy.yyy.yyy).

どうも、START TLSの関係らしい。色々調べてみたが、取敢えず、以下の方法で解決はした。

await cli.ConnectAsync("smtp.boo.foo",25,SecureSocketOptions.None);

しかし、最初の様なコードを持つアプリが結構あるため、SMTPサーバー側でどうにかならないか調べてみたところ、sendmailの場合だと、accessに下記の設定を行なえば、「SMTPサーバーはSTART TLSをサポートしない」という宣言?になるらしい。

# Check the /usr/share/doc/sendmail/README.cf file for a description
# of the format of this file. (search for access_db in that file)
# The /usr/share/doc/sendmail/README.cf is part of the sendmail-doc
# package.
#
# If you want to use AuthInfo with "M:PLAIN LOGIN", make sure to have the
# cyrus-sasl-plain package installed.
#
# By default we allow relaying from localhost...
Srv_Features:   S
・・・

この設定を行ない、sendmailを再起動した後に、元のコードでメール送信を行なったところ、正常にメールが送信された。

社内でしか使用しないSMTPリレーサーバーなので、TLSは必要ないため、これで良しとしよう。

でも、この方法見つけたのは、結構な数のアプリコード直した後だったりする。Orz

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

.net 8 RC2 blazor dialogタグ

.net 8 RC2からdialogタグのイベントとして、@oncloseと@oncancelが追加された。これにより、ダイアログのクローズ時とESCキーによるキャンセル時にC#コードを呼び出すことができるようになった。

@oncloseはダイアログが閉じた時、@oncancelはESCキーでダイアログを閉じたときに呼び出される。(@oncloseイベントはCancel時でも呼び出されるので、ESCキーでダイアログを閉じた時には、@oncancel⇒@oncloseの順で呼び出される。)

以下に例を挙げる

<button class="btn btn-primary" onclick="dlg.showModal()">ダイアログ表示</button>
<br/>
<hr/>
<pre>@Message</pre>
<hr/>
<dialog id="dlg" @onclose="OnDialog_Close" @oncancel="OnDialog_Cancel">
    <form method="dialog">
        <table>
            <tr>
                <td><span style="font-size:24pt;font-weight:bold">Hello World</span></td>
            </tr>
            <tr>
                <td> </td>
            </tr>
            <tr>
                <td>
                    <button class="btn btn-primary" @onclick="OnOK_Click">OK</button>
                </td>
            </tr>
        </table>
    </form>
</dialog>

@code {
    protected string Message = "";
    int cnt;
    protected override void OnInitialized() {
        cnt = 1;
    }
    protected void OnOK_Click() {
        Message += $"OK Button Click({cnt})->";
    }
    protected void OnDialog_Close() {
        Message += $"Dialog Close({cnt}) ";
        cnt++;
    }
    protected void OnDialog_Cancel() {
        Message += $"Dialog Cancel({cnt})->";
    }
}

以下の実行例のように、OKボタンでクローズされた場合は、@closeで指定されたイベントハンドラのみ実行され、ESCキーで閉じた場合は@cancel⇒@onclickの順でイベントハンドラが実行されている。

まぁ、どちらかと言えば、Dialog中のボタンのハンドラで処理する方が多いかもしれないけれど・・・

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

.NET 8 RC1 Blazor

.NET 8 RC1(正確にはPreview 6?)からblazorserverプロジェクトテンプレートが無くなってしまった。

.NET Blogによると、

.NET 8 では、Web UI のすべてのニーズに Blazor コンポーネントのフルスタックを使用できるように、Blazor に機能を追加してきました。 要求に応じてサーバーから Blazor コンポーネントを静的にレンダリングし、強化されたナビゲーションとフォーム処理でエクスペリエンスを段階的に強化し、サーバーでレンダリングされた更新をストリーミングし、Blazor Server または Blazor WebAssembly を使用して必要な場合に豊かな対話機能を追加できるようになりました。 アプリの読み込み時間を最適化するために、Blazor は実行時に Blazor Server と Blazor WebAssembly のどちらを使用するかを自動選択することもできます。

とのこと。

Server SideのBlazorコンポーネントを動かすには、blazorプロジェクトテンプレートを使用する必要がある。このテンプレートのオプションを見ると、デフォルトでServer Sideを使用するらしい。

-uw, –use-wasm Configures whether to support rendering components interactively in the browser using WebAssembly.
The default value is false.
Type: bool
Default: false
-us, –use-server Configures whether to support rendering components interactively on the server via a SignalR WebSocket connection.
The default value is true.

Type: bool
Default: true

で、blazorテンプレートでコンポーネントを作ってみたのだが、イベントのハンドリングとかが全然動作しない。.NET Blogの記事を見てもよくわからないし、他の記事でもRC1以前のものばかりで良く分からない。

仕方ないので、サンプルのCounterコンポーネントを見てみると、先頭に以下のようなディレクティブが・・・

@page "/counter"
@attribute [RenderModeServer]

どうもこの属性を指定しないと、サーバーサイドでレンダリングされないようだ。

とりあえず、この属性を指定したら、コンポーネントが動作するようになった。

結構、破壊的な変更だよね。

カテゴリー: .NET, asp.net core, Blazor, 技術系 | 2件のコメント

T-SQLの小技(メモ)

T-SQLの小技?を一つ。

MySQLでは、以下のようにSELECT文にLIMIT句を指定して、クエリ結果から指定オフセットから指定件数分取得することが可能。

SELECT ・・・ FROM TABLE_NAME ・・・ LIMIT 10,5 -- クエリ結果の5行目から10行分
-- または
SELECT ・・・ FROM TABLE_NAME ・・・ LIMIT 10 OFFSET 5 -- クエリ結果の5行目から10行分

ではT-SQLで同じ事を行なう場合はどうすれば良いのか?

昔はスマートなやり方はなかったが、SQL Server 2012からは以下の構文で取得が可能となったようだ。(但し、ORDER BY句の一部だが・・・)

-- クエリ結果の@START_OFFSET行目から@FETCH_NUMBER行分取得
SELECT COLUMN_1[,COLUMN_2[,...]] FROM TABLE_NAME
  ・・・
  ORDER BY KEY_COLUMN
  OFFSET @START_OFFSET ROWS
  FETCH NEXT @FETCH_NUMBER ROWS ONLY

※↓EFでSQL Serverに対して.Skipや.Takeを使用すると、上記と同様なSQLが作成された。

var q = ctx.TABLE_NAME.OrderBy(v=>v.KEY_COLUMN).Skip(n).Take(m);
SELECT ・・・
      FROM TABLE_NAME AS [T]
      ORDER BY [T].[KEY_COLUMN]
      OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY

これって結構使う機能だと思うのだけど、T-SQLでサポートされていたとは知らなかった(^^;

カテゴリー: T-SQL, 技術系 | 2件のコメント

Microsoft Graph Powershell Moduleを使用したライセンス付与

MSOnline PowershellモジュールのSet-MsolUserLicenseコマンドレットが非推奨になった。

使用することは未だ可能だが、スクリプトなどで連続処理を行なうと、3~4件ぐらい処理した後に、下記のエラーが発生して、次の処理を行えるまでにかなりの時間(分のオーダー)が掛かり実用的ではない。

You have exceeded the maximum number of allowable transactions. Please try again later

仕方が無いので、Microsoft Graph APIを使用した方法を試してみた。

ただ、面倒なのはライセンスを追加するにしろ削除するにしろ、Microsoft Graph APIと同じ形で、追加,削除双方のパラメータが必須なところ。

一々、手で打ち込むのは大変なので、下記のようなfunctionを作成してみた。

# MS Graphを使用してライセンスの付与
# skunameはEXCHANGESTANDARDやENTERPRISEPACK等
function global:AddLicense($user,$skuname) {
	# 追加するサブスクリプションライセンスのSkuId(GUID)を取得
	$local:sku = get-mgsubscribedsku -all | where-object {$_.SkuPartNumber -eq $skuname}
	$lic = @(
		@{
			SkuId = $sku.SkuId
			DisabledPlans = @()
		}
	)
	# UserIDを取得
	$local:uid = get-mguser -userid $user
	# ライセンス付与(-RemoveLicensesパラメータも必須-空のリストを渡す)
	set-mguserlicense -userid $uid.Id -addlicenses $lic -removelicenses @()
}
# MS Graphを使用してライセンス削除
# skunameはEXCHANGESTANDARDやENTERPRISEPACK等
function global:RemoveLicense($user,$skuname) {
	# 削除するサブスクリプションライセンスのSkuId(GUID)を取得
	$local:sku = get-mgsubscribedsku -all | where-object {$_.SkuPartNumber -eq $skuname}
	# UserIDを取得
	$local:uid = get-mguser -userid $user
	# ライセンス削除(-AddLicensesパラメータも必須-空のリストを渡す)
	set-mguserlicense -userid $uid.Id -AddLicenses @() -RemoveLicenses @($sku.SkuId)
}

ちなみに、Microsoft Graph Powershell Moduleのコマンドレットを使用する前には、Connect-MgGraphコマンドを実行しておく必要がある。

Connect-MgGraphコマンドには、-Scopesパラメータで、User.Read.All,User.ReadWrite.All,Directory.Read.All,Directory.ReadWrite.All等、コマンド実行に必要な権限を指定する必要がある。

実際に試してみて、単体では問題無く処理ができた。また、連続処理も(同じアカウントにだが)追加,削除をfor文で10回繰り返してもエラーが出なかったので、多分、問題は無いと思う。

カテゴリー: Microsoft Graph, PowerShell, 保守・運用, 技術系 | コメントする

MS Graph APIを使用したTeamsへのメッセージ投稿

今回は、MS Graph APIを使用して、TeamsのChannelへメッセージを投稿する方法について試してみた。

前提として、投稿を行うには、TeamのIDとChannelのIDが必要となる

また、メンション付きメッセージにする場合は、Channel全体へのメンションはChannelのID,個人向けのメンションはADユーザーのObject IDが必要。ユーザーの場合はUserPrincipalNameでは無いので注意が必要。

投稿には、下記の権限が必要。

  • ChannelMessage.Send
  • Group.ReadWrite.All(下位互換性のためにのみサポート)

まず、投稿用のメッセージを作成する。

var msg = new ChatMessage
{
    Subject = "テストメッセージ",
	Body = new ItemBody
	{
		// メンションありの場合はHTMLにする必要あり
        ContentType = BodyType.Html,
		Content = """
        <at id="0">メンション先</at><br/>
        !!!テストです!!!<br/>
        """,
	},
	// 重要度
    Importance = ChatMessageImportance.High,
	// メンションの対象指定
    Mentions = new List<ChatMessageMention>() {
        new ChatMessageMention() {
            Id = 0,	// <at id="0">
            MentionText = "<メンション先の表示名>",
            Mentioned = new ChatMessageMentionedIdentitySet() {
				/* Channelにメンションする場合
                Conversation = new TeamworkConversationIdentity() {
                    Id = "<Channel ID>",
                    DisplayName = "<表示名>",
                    ConversationIdentityType = TeamworkConversationIdentityType.Channel
                }
				*/
                // ユーザーにメンションする場合
                User = new Identity() {
                    DisplayName = "<表示名>",
                    Id = "<ユーザーID>",    // UserPrincipalNameではなく、ObjectIdが必要!!
                }
            }
        }
    }
};

次にメッセージを投稿する。

※メッセージの送信元はGraphにログインしたユーザーとなる。

// Chanelにメッセージ投稿
await cli.Teams[<Team ID>]
        .Channels[<Channel ID>]
        .Messages
        .PostAsync(msg);
結果

ちなみに、メンション先の指定である、ChatMessageMention中のMentionTextと本文中の
<at id="・・・">[表示名]</at>[表示名]が合っていないと、例外が発生するようだ。この[表示名]は実際のユーザーの表示名やChannel名と異なっていても、メッセージ上には[表示名]が表示される。(ChatMessageMentionedIdentitySet中のDisplayNameは無視されるようだ・・・)

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

blazorでの画面遷移方法など

最近、Blazor(Server Side)でアプリを作っていて、チョットばかり調べたものがあるので、備忘録として。

まず、リンクを使用せずに、ボタンイベント等でコードを使用して、他ページに画面遷移させる方法。

これには、NavigationManagerクラスを使用する。NavigationManagerクラスはデフォルトで、サービス登録されているので、injectして使用する。

@page "/"
・・・
@inject NavigationManager Navi
・・・
<button class="btn btn-primary" @onclick="GotoNext">次ページ</button>
@code {
	・・・
	protected void GotoNext() {
		// Nextページへ遷移
		Navi.NavigateTo("./Next");
	}
	・・・
}

お次は、BlazorコンポーネントへURLベースでパラメータを渡す方法。

URLの一部をパラメータとして渡すには、pageディレクティブを以下のように定義し、コード部でパラメータとして定義する。

@page "/Edit/{UserID?}"
・・・
@code {
	・・・
	[Parameter]
	public string? UserID { get; set; }
	・・・
}

./Edit/User001のような感じで呼び出すと、UserIDに”User001″がセットされる。

クエリパラメータをコンポーネントに渡す場合は以下のような感じ。

@page "/Edit"
・・・
@code {
	[Parameter]
	[SupplyParameterFromQuery]
	public string? UserID {get; set;}
}

./Edit?UserID=User001のような感じで呼び出す。

意外と忘れそうなので、メモしとく。

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

blazor serversideでのセッション変数

asp.net blazor server sideでセッション変数を使おうとして、チョット嵌ったので、メモ。

asp.net webappなどでは、Program.cs中でServiceにSessionを追加して、UseSessionメソッドを呼び出し、プログラム中からは、HttpContext.Sessionを介して、セッション変数を扱えるが、blazor server sideの場合、HttpContextにアクセスできないようなので、どうすればよいのか調べてみると、以下のような感じでセッション変数を保存,参照,削除できるようだ。

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage Session
・・・
// セッション変数操作(キーは当然string)
// セッション変数へ値を保存(このメソッドのValueはobject型)
await Session.SetAsync(Key,Value);

// セッション変数から値を取得
// valの型はProtectedBrowserStorageResult<T>となる
var val = await Session.GetAsync<T>(Key);
if (!val.Success) {
	// 取得失敗
}
T? v = val.Value;

// セッション変数を削除
await Session.DeleteAsync(Key);

ただ、このメソッド群はJavascriptを使用しているようで、OnInitializedAsync内で呼び出すと、例外が発生する。

初期化時にセッション変数の値を得たい場合は、OnAfterRenderAsync中に記述する必要がある。

普通にHttpContextが取れればいいのに・・・まぁ、Blazorの仕組み上難しいのかな。

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