C#13 paramsコレクション

.NET9.0でC#はVersion 13になった。
Version 13での新機能は余り目立ったものは無いが、
paramsコレクションは使えそうだ。

paramsとは、メソッドの同じ型の引数を可変にするもので、C# 12までだと、
以下のような感じで、配列しか指定できなかった。。

T f(T1 p1, T2 p2, ... , params T3[] p) {}

C#13からはコレクション全般が使用できるようになった。
これによりどのような利点があるかというと、以下のようなパターンを全て1つの定義でまかなえるようになることだ。

> string JoinString(string delim , params IEnumerable<string> prm) => string.Join(delim,prm);
> JoinString("," , "Paul","John","George","Ringo")		// 引数の列挙
"Paul,John,George,Ringo"
> JoinString("," , ["Paul","John","George","Ringo"])	// 配列
"Paul,John,George,Ringo"
>List<string> lst = ["Paul","John","George","Ringo"];
> JoinString("," , lst)									// リスト
"Paul,John,George,Ringo"
>JoinString("," , lst.Where(v=>v.Contains("o")))		// クエリ
"John,George,Ringo"

チョット使えそうなのでメモ。

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

.NET 9 MAUI Blazor Web

11/12に.NET9が正式リリースされた。

MAUI関連で、下記プロジェクトテンプレートが増えていた。

.NET MAUI Blazor Hybrid and Web App maui-blazor-web

元々ある、”.NET MAUI Blazor アプリ maui-blazor”はMAUIアプリでBlazorコンポーネントを使えるようにするプロジェクトテンプレートだが、これは何かと思って、早速プロジェクトを作成すると、Blazor WebサイトとMAUI Blazor Hyblidの双方で、コンポーネントを共有するプロジェクトテンプレートだった。

このプロジェクトテンプレートを使用すると、MAUIアプリプロジェクト,とBlazor Webアプリプロジェクト、共有コンポーネントプロジェクトの3つが作成され、MAUIアプリ,Blazor Webアプリの双方からコンポーネントが共有するためのスケルトンが作成される。

チョット気になったのでメモ。

カテゴリー: .NET, Blazor, MAUI, 技術系 | 1件のコメント

調子に乗って・・・

Mac mini M4(最低スペック)がamazonで本家サイトより、かなり安かったので調子に乗って買ってしまった。思ったより小さい。

Macなんぞ、使うのは20年以上ぶりだから、イマイチ使い勝手がわからない。

まず、LogiCoolのキーボードをWindowsと共有しているので、cmdだとかctrlとか・・・

一番困るのは日本語入力切替だね。もう、色々ググりまくり。最悪、マウスでどうにか切替は出来るけど・・・

当然、VSCodeと.NET SDKは入れて、動作確認はOK。動作は速い事は速いね。

すっかり忘れていたけど、メニューバーが画面の一番上でアプリが占有するのは未だにそうなんだな、これは頂けない・・・VSCode起動して、「あれ、メニューが出てこないぞ?」と思ったよ・・・

後、VSCodeをコマンドラインから起動する方法が分からなくて調べたら、VS Codeのコマンドパレットから、「shell command: install ‘code’ command in PATH」を実行しなければならない。この辺りはちょっと面倒だね。

とりあえず、色々、イジってみよう。

ちなみに、この投稿はMacから・・・

カテゴリー: Mac, 日記的なもの | 1件のコメント

QuickGridについて

.NET 8.0 BalzorでQuickGridが正式にサポートされた。

QuickGridはBlazorで使用可能な、Gridコンポーネントで、IQueryable<T>のデータソースの表示が可能で、項目毎のソートや、表示方法などを指定できる。

詳しくは、以下のリンクを参照してほしい。

ASP.NET Core Blazor QuickGrid コンポーネント | Microsoft Learn
Typical usage

ここでは、簡単な使用例を参考として上げておく。

まず、プロジェクトに下記パッケージを追加

> dotnet add package Microsoft.AspNetCore.Components.QuickGrid

QuickGridのデータソースにEntityFrameworkCoreを使用する場合には、下記も(今回の例では未使用)

> dotnet add package Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter

サンプルを以下に示す。(MongoDB使用)

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Bl8Zoo.Common;
/// <summary>
/// Animal
/// </summary>
public class Animal
{
    // 名称
    public string Name { get; set; } = null!;
    // イメージ
    public byte[] Picture { get; set; } = null!;
}
/// <summary>
/// MongoDBストア形式
/// </summary>
public class AnimalDb {
    // ID
    [BsonId]
    public BsonObjectId _id { get; set;} = null!;
    // 名称
    public string Name { get; set; } = null!;
    // GridFS上のファイルId
    public BsonObjectId ImageId { get; set; } = null!;
    // メモ
    public string? Memo { get; set; }
}
@page "/"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.QuickGrid
@using Bl8Zoo.Common
@using MongoDB.Driver
@using MongoDB.Driver.GridFS

<h2>Zoo</h2>
<br/>
<div style="display:flex;justify-content: center;flex-direction: column;">
    <!-- 
        QunickGridの実装例
        ItemsにIQueryable<T>を指定する。
        Paginationによって、ページング単位を指定できる
    -->
    <QuickGrid Items="@Animals" Pagination="@pagination">
        <!-- 
            PropertyColumnはAnimalsの各要素を直接描画したり、書式化出力するのに使用する
            Sortable="true"とすることで、ソートが可能となる
        -->
        <PropertyColumn Property="@(c=>c.Name)" Sortable="true"/>
        <!-- TemplateColumnを使用して、イメージを表示 -->
        <TemplateColumn Title="Image">
            <!-- 
                contextはAnimalsの各要素として使用できる
                ここでは、Picture(バイト配列)をDataURLとしてイメージを表示している
            -->
            <img src="data:image/jpeg;base64,@Convert.ToBase64String(context.Picture)"/>
        </TemplateColumn>
    </QuickGrid>
    <!-- Paginatorによって、ページの切替が可能 -->
    <Paginator State="@pagination" />
</div>
@code {
    // Pagination設定(1ページ2項目)
    protected PaginationState pagination = new PaginationState { ItemsPerPage = 2 };
    // Items
    protected IQueryable<Animal> Animals = null!;
    /// <summary>
    /// 初期化処理
    /// </summary>
    /// <returns></returns>
    protected override async Task OnInitializedAsync() {
        // MongoDBからデータ取得
        var cli = new MongoClient();
        var db = cli.GetDatabase("AnimalDb");
        var col = db.GetCollection<AnimalDb>("Animals");
        var fnd = col.AsQueryable<AnimalDb>();
        List<Animal> animals = new();
        GridFSBucket bkt = new GridFSBucket(db);
        foreach(var itm in fnd) {
            Animal ani = new Animal() { Name = itm.Name};
            // GridFSからイメージのダウンロード
            ani.Picture = await bkt.DownloadAsBytesAsync(itm.ImageId);
            animals.Add(ani);
        }
        Animals = animals.AsQueryable();
    }
}

実行例

ページングもちゃんとできてる。名前でソートしてみると、

ちなみに、EntityFrameworkAdapterを使用してEF Coreと連携すると、ページ移動,ソートなどは、SQLを生成してクエリ結果を反映させるようになっていた。(以下参照)

@page "/"
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore
@using GridEF.Db
@using Microsoft.AspNetCore.Components.QuickGrid
@using NodaTime
@inject PersonContext ctx

<h2>QuickGrid+Entity Framework Core</h2>
<p>
    QuickGridのItemsは下記のようにDbのテーブル<br/>
    IQueryable<Person>? personel = ctx.Personel;<br/>
    PersonelテーブルはPerson型の集合<br/>
    <pre>
    public class Person {
        [Key]
        public int Id { get; set; }
        [Required]
        public string Name { get; set; } = null!;
        public DateOnly Birthday { get; set; }
    }
    </pre>
    Ageは計算で求めている。<br/>
    <pre>
    <QuickGrid Items="@@personel" Pagination="@@paginator">
        <PropertyColumn Property="@@(c=>c.Name)" Sortable="true"/>
        <PropertyColumn Property="@@(c=>c.Birthday)" Format="yyyy/MM/dd" Sortable="true"/>
        <PropertyColumn Property="@@(c=>GetAge(c.Birthday))" Title="Age" />
    </QuickGrid><br/>
    </pre>
</p>
<QuickGrid Items="@personel" Pagination="@paginator">
    <PropertyColumn Property="@(c=>c.Name)" Sortable="true"/>
    <PropertyColumn Property="@(c=>c.Birthday)" Format="yyyy/MM/dd" Sortable="true"/>
    <PropertyColumn Property="@(c=>GetAge(c.Birthday))" Title="Age" />
</QuickGrid>

<Paginator State="@paginator"/>

@code {
    IQueryable<Person>? personel;
    PaginationState paginator = new PaginationState() { ItemsPerPage = 5};
    protected override void OnInitialized() {
        personel = ctx.Personel;
    }
    protected int GetAge(DateOnly dt) {
        return Period.Between(
            LocalDate.FromDateOnly(dt),
            LocalDate.FromDateTime(DateTime.Now))
            .Years;
    }
}
#Sqlite使用
#IQueryable<Person>? personel; // Items
#personel = ctx.Personel; // Db Table
#ページ移動時
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT "p"."Id", "p"."Birthday", "p"."Name"
      FROM "Personel" AS "p"
      LIMIT @__p_0 OFFSET @__p_0

#Nameでソート
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT "p"."Id", "p"."Birthday", "p"."Name"
      FROM "Personel" AS "p"
      ORDER BY "p"."Name"
      LIMIT @__p_0 OFFSET @__p_0

#Birthdayでソート
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT "p"."Id", "p"."Birthday", "p"."Name"
      FROM "Personel" AS "p"
      ORDER BY "p"."Birthday"
      LIMIT @__p_0 OFFSET @__p_0

結構いろいろ使えそうなのでメモ。

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

BlazorテンプレートにWebAppを同居させたい

BlazorのプロジェクトテンプレートにWebAppを同居させるにはどうすれば良いか?

MS謹製のガイドではWebAppを使用するために行う事が書かれているので、それに基づいて試してみた。

まず、Program.csを下記のように変更する。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

// 追加
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseRouting();       // 追加
app.UseAuthorization(); // 追加(認証関係なければ無くても良い)

app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.MapRazorPages();    // 追加(<AppRoot>/Pages下をマッピング)

app.Run();

<AppRoot>下にPagesフォルダを作成し、そこに、.cshtmlと.cshtml.csを置く。

中身はこんな感じのもの。

@page
@using BlWebTest.Pages

@model TestPage

<h2>Test Page under /Pages</h2>
<h3>@Model.NowDate</h3>
namespace BlWebTest.Pages;

using Microsoft.AspNetCore.Mvc.RazorPages;
public class TestPage : PageModel {
    public DateTime NowDate;
    private readonly ILogger<TestPage> _logger;

    public TestPage(ILogger<TestPage>logger) {
        _logger = logger;
        NowDate = DateTime.Now;
    }

}

実行してみる。

http://・・・/TestPage

表示された。

asp.net 7以前のBlazorServerプロジェクトテンプレートとBlazorプロジェクトテンプレートでは作成されるフォルダ構成が違うので、どうすれば良いのかな?と思ったので、試してみた。

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

.net DIの有効期間

.netのDI(Dependency Injection)は指定方法によって、その有効期間が異なる。

Blazorを使って、その有効期間を見ていこう。

まず、以下のようなクラスを用意する。

public class SingletonClass {
    public int Count { get; set; }
    public EventHandler? SingletonEvent { get; set; }
    public void BubbleSingletonEvent(object? sender, EventArgs e) {
        if (SingletonEvent != null) {
            SingletonEvent.Invoke(sender,e);
        }
    }
}
public class ScopedClass {
    public int Count { get; set;}
}
public class TransientClass {
    public int Count { get; set;}
}

これらのクラスをサービスとして登録

・・・
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
// Singleton
builder.Services.AddSingleton<SingletonClass>();
// Scoped
builder.Services.AddScoped<ScopedClass>();
// Transient
builder.Services.AddTransient<TransientClass>();
・・・

BlazorサンプルのCounterコンポーネントで、これらをInject

@page "/counter"
@rendermode InteractiveServer
@inject SingletonClass sic
@inject ScopedClass scp
@inject TransientClass trn

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>
<p role="status">Singleton: @sic.Count</p>
<p role="status">Scoped: @scp.Count</p>
<p role="status">Transient: @trn.Count</p>

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

@code {
    private int currentCount = 0;

    protected override void OnInitialized() {
        // Singletonインスタンスへイベントハンドラを設定
        sic.SingletonEvent += (sender,e) => this.InvokeAsync(this.StateHasChanged);
    }

    private void IncrementCount()
    {
        // それぞれインクリメント
        currentCount++;
        sic.Count++;
        scp.Count++;
        trn.Count++;
        // Singletonインスタンスのイベントを発生させる
        sic.BubbleSingletonEvent(this,new EventArgs());
    }
}

実行すると・・・

このように、Singleton,Scopedの場合は、ページ遷移しても、インスタンスが保持されるが、Transientの場合はページ遷移すると、新しいインスタンスが作成される。

また、Singletonの場合はアプリケーション全体でSingletonとなるので、別インスタンスでも共有される。

別インスタンス

この例では、Singletonにイベントハンドラを設定して、ボタンクリック時にイベントを発生させているため、別インスタンスでボタンがクリックされた場合、Singletonのカウントがアップされて表示される。

DIの動作が気になったので、メモ。

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

MS Graph APIを使用したメールメッセージ検索と削除

ExchangeOnline上で特定の送信者からのメッセージを全削除したいと思い、MS Graphを使用してメッセージ一覧を取得するプログラムを書いてみた。

最初は、$filterを使用するつもりだったが、シンタックスがよく分からなくて断念。で、調べてみると、$filterより$searchの方が柔軟性がありそうなので、そちらを使用することにした。

$searchの対象だが、メッセージの場合、下記の物を検索対象として使用できる。

attachment添付ファイル名
ex) attachment:.ppt
bcc検索対象がBcc宛先メールアドレスあるいは氏名のフル指定または一部
ex) bcc:xxx@yyy等
bodyメールメッセージ本文
ex) body:テスト
cc検索対象がCc 指定方法はBcc同様
ex) cc:xxx
from検索対象が送信元 指定方法はBccと同様
ex) from:xxx@yyy.com
hasAttachment添付ファイル有/無(true/false)
ex) hasAttachment:true
importance重要度(low,midum,high)
ex) importance:high
kind種別(docs, email, faxes, im, journals, meetings, notes, posts, rssfeeds, tasks, voicemail)
ex) kind:email
participants検索対象:to, cc, bcc(指定方法はbccと同様)
received検索対象は受信日付
ex)
日付指定received:MM/DD/YYYY
範囲指定received>=MM/DD/YYYY AND received<=MM/DD/YYYY
recipientsparticipantsと同様
sent検索対象は送信日付。指定方法は受信日付と同様
size検索対象はメッセージサイズ(バイト数)
ex)
size:1..5000
size>=10000
subject検索対象は件名
ex) subject:テスト
to検索対象はTo 指定方法は(bccと同様)
ex) to:xxx@yyy.co.jp
検索対象と指定方法

上記はANDやORによって結合可能。()で優先順位の変更もできる。詳しくは下記を参照。
Use the $search query parameter in Microsoft Graph – Microsoft Graph | Microsoft Learn
Message properties and search operators for In-Place eDiscovery in Exchange Server | Microsoft Learn

例)送信元ドメインがxxx.co.jpまたはyyy.comで2024/5/10以降に受信したメッセージを検索

“(from:xxx.co.jp OR from:yyy.com) AND received>=05/10/2024”

C#からMS Graph APIにこれらの条件を設定するには、メッセージの取得時にQueryParamters.Searchに渡す形となる。

// Graph Client取得
var cli = new GraphServiceClient(cred);
・・・
//
string query = $"(from:{dom1} OR from:{dom2}) AND received>={FromDate.ToString("MM/dd/yyyy")}";
// メールメッセージ検索
var res = await cli.Me.Messages.GetAsync(
    opt=>{
        opt.QueryParameters.Search = @$"""{query}""";
        opt.QueryParameters.Select = ["id","sender","subject","receivedDateTime"];
        opt.QueryParameters.Top = 2000;
    }
);

という感じでメッセージを取得して、同時に取得したメッセージのIdを利用して削除することができた。

削除はDeleteAsyncを使用して行なう。一応、下記にコードを書いておく。

foreach(var itm in res.Value) {
    Console.WriteLine($"{itm.Sender?.EmailAddress?.Address},{itm.Subject}");
    await cli.Me.Messages[itm.Id].DeleteAsync();
}

※CLIで試験後、Blazorでログイン⇒メッセージ検索+選択削除のアプリを作成した。

忘れない様にメモ。

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

MAUI Blazor

以前、Blazor Hybridの例で、Windows FormsとBlazorを試してみたが、今回はMAUIで試してみた。

MAUIの場合も、Windows Formsと同様にMAUI~Blazor間の連携が可能。

共有する、Singletonインスタンスを以下のような感じで作成し、MauiProgram.cs内でサービス登録することで、相互で同じインスタンスを共有できる。

namespace MAUIBlApp;

public interface IHostIF {
    /// <summary>
    /// 共有データ
    /// </summary>
    /// <value>カウント値</value>
    public int Count { get; set; }
    /// <summary>
    /// クライアントイベントハンドラ
    /// </summary>
    public event EventHandler ComponentEvent;
    /// <summary>
    /// クライアント側からイベント発生
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public void FireClientEvent(object sender, EventArgs e);
    /// <summary>
    /// ホストイベントハンドラ
    /// </summary> <summary>
    /// 
    /// </summary>
    public event EventHandler HostEvent;
    //
    /// <summary>
    /// ホスト側からイベント発生
    /// </summary>
    /// <value></value>
    public void FireHostEvent(object sender, EventArgs e);
}
/// <summary>
/// IHostIFの実装
/// </summary>
public class HostInterface : IHostIF {
    public int Count { get; set; } = 0;
    public event EventHandler ComponentEvent = null!;
    public void FireClientEvent(object sender, EventArgs e) {
        ComponentEvent?.Invoke(sender,e);
    }
    public event EventHandler HostEvent = null!;
    public void FireHostEvent(object sender, EventArgs e) {
        HostEvent?.Invoke(sender,e);
    }
}
using Microsoft.Extensions.Logging;

namespace MAUIBlApp;

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
			});

		HostInterface hst = new HostInterface();
		builder.Services.AddSingleton<IHostIF>(hst);
		builder.Services.AddMauiBlazorWebView();

・・・
namespace MAUIBlApp;

public partial class App : Application
{
	public App(IHostIF hif)
	{
		InitializeComponent();
		// MainPageのロード(IHostIF(Singleton)インスタンスを渡す)
		MainPage = new MainPage(hif);
	}
}
namespace MAUIBlApp;

public partial class MainPage : ContentPage
{
	private readonly IHostIF _hostif;
	/// <summary>
	/// コンストラクタ(IHostIFのインスタンスを取得)
	/// </summary>
	/// <param name="hostif"></param>
	public MainPage(IHostIF hostif)
	{
		InitializeComponent();
		_hostif = hostif;
		// Client(Blazor Component)イベントハンドラ設定
		_hostif.ComponentEvent += OnClientEvent;
		lblNumber.Text = _hostif.Count.ToString();
	}
	/// <summary>
	/// ボタンクリック時処理
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	public void OnBtnClick(object sender, EventArgs e) {
		// インターフェースのカウントアップ
		_hostif.Count++;
		lblNumber.Text = _hostif.Count.ToString();
		// Client(Blazor Component)へイベント通知
		_hostif.FireHostEvent(sender,e);
	}
	/// <summary>
	/// Client(Blazor Component)イベントハンドラ
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	public void OnClientEvent(object? sender, EventArgs e) {
		lblNumber.Text = _hostif.Count.ToString();
	}
}
@page "/"

@inject IHostIF HostInterface

<h2>Counter</h2>

<p role="status">Current count:
@if (Numbers != null && Numbers.Count != 0) {
    foreach(var v in Numbers) {
        string imgname = $"/images/number_{v}.png";
        <img src="@imgname" style="width:42px;height:49px"/>
    }
}
</p>
<br/>

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

@code {

    /// <summary>
    /// カウンタのイメージIndex
    /// </summary>
    /// <returns></returns>
    protected List<int> Numbers = new () {0};

    /// <summary>
    /// Initialize
    /// </summary>
    protected override void OnInitialized() {
        // Hostイベントのハンドラ設定
        HostInterface.HostEvent += async (_,_) => await InvokeAsync(()=>{CreateNumberImages();StateHasChanged();});
    }
    /// <summary>
    /// ボタンイベント
    /// </summary>
    private void IncrementCount()
    {
        // Interfaceのカウントをインクリメント
        HostInterface.Count++;
        // カウンタ⇒イメージ番号の作成
        CreateNumberImages();
        // ホストに通知
        HostInterface.FireClientEvent(this,new EventArgs());
    }
    /// <summary>
    /// イメージ番号の生成
    /// </summary>
    private void CreateNumberImages() {
        Numbers = new();
        string s = HostInterface.Count.ToString();
        for(int i=0;i < s.Length; i++) {
            Numbers.Add(Convert.ToInt32(s[i].ToString()));
        }
    }
}

以下のように、MAUI側からでもBlazorコンポーネント側からでも同じインスタンスを参照していることが分かる。

最初、MainPageの呼出でコンパイルエラーが出ていて悩んだが、よくよくエラーを見ると、MainPage.xaml.csでは無く、App.xaml.csでMainPageインスタンスを作成するときに、インターフェイスインスタンスを渡していないエラーだった。

Appのコンストラクタにインターフェイスインスタンスをパラメータで指定することにより、サービス登録されたSingletonインスタンスを取得できるので、これをMainPage作成時のパラメータとして渡すことで解決できた。

※MAUIをVS Codeで作成するなら「.NET MAUI拡張」を入れておくと便利。

カテゴリー: .NET, Blazor, C#, MAUI, 技術系 | 2件のコメント

ASP.net WebAPIプロジェクトテンプレート

久しぶりに、WebAPIテンプレートを使ってみたら、デフォルトがuse-minimal-apisになっていて、コードが以下のようにProgram.csに全て入っているコードが生成されてビックリした。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
	・・・
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

今まで通り、Controllerを使用するには、下記のように、dotnet newにオプションを指定する必要があるようだ。

dotnet new webapi -o <プロジェクト名> --use-controllers
#または
dotnet new webapi -o <プロジェクト名> -controllers
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

デフォルトでこちらにして欲しいんだけど・・・

何かminimal apiだと、Node.jsみたいだし、Program.csにコードを全て書くのは好きじゃないなぁ。

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

PowershellからOffice365のパスワードをリセットする

PowershllからOffice365 Azure ADのパスワードをリセット(変更)する方法は、MSOnlineモジュールのSet-MsolUserPasswordを使用する方法と、Micorosoft Graph APIモジュールのUpdate-MgUserを使用する方法が一般的だと思う。

Set-MsolUserPasswordでは、下記のように、パスワードを指定しないと、自動でジェネレーとしてくれるので楽。

# MSOnline Module
Set-MsolUserPassword -UserPrincipalName boo@foo.com -ForceChangePassword

ただ、MSOnlineモジュールでは、ライセンス付与のコマンドレット等が非推奨となっており、使用するには色々制限があるので、MSOnlineモジュールを使用して作成したスクリプトをMS Graph APIモジュールに置き換えている最中。

MS Graph APIを使用してパスワードをリセットするには、Update-MgUserコマンドレットを使用する。ユーザー情報そのものの更新になるので、更新用のパラメータボディを作成する必要があるのと、パスワードの自動ジェネレートをサポートしていないので、その部分を用意してあげる必要がある。まぁ、関数化しておくのが無難かな。

# Microsoft Graph API Module
$prm = @{
  passwordProfile = @{
    password = MakePassword # パスワード生成
    forceChangePasswordNextSignIn = $true # 次回サインイン時にパスワード変更を強制
  }
}
Update-MgUser -UserId boo@foo.com -bodyparameter $prm

ちなみに、他人のユーザー情報を更新するには、Connect-MgGraphコマンドレットのscopeに”Directory.ReadWrite.All,Directory.AccessAsUser.All”が必要となる。

忘れない様にメモ。

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