MS Graphでテナントのライセンスを取得

Powershell Office365モジュールでは、簡単にテナントのライセンス情報を取得できるのだが、プログラムからMS Graphで取得するにはどうすればよいか、調べてみた。

調べ方が悪いのか、あまり例がヒットしなかったが、以下のような感じで取得できることを確認した。

まず、Azure AD上のアプリケーションに必要な、アクセス許可は以下の通り。

  • Origanization.Read.All
  • Directory.ReadAll
  • Organization.ReadWrite.All
  • Directory.ReadWrite.ALl

なぜ、Writeが必要なのかは不明・・・

取得方法だが、GraphへアクセスするためのServiceClientを作成して、.SubscribedSkus.GetAsync()メソッドを呼び出すだけ。

戻り値として、契約しているライセンス情報一覧が返される。

// GraphServiceClientの作成(Credentialの作成方法は省略)
var cli = new GraphServiceClient(cred);
// 契約しているライセンス情報一覧の取得
var lic = await cli.SubscribedSkus.GetAsync();

上記の場合、lic.ValueにList<SubscribedSku>?が入ってくる。SubscribedSkuには様々な情報が入ってくるが、特によく使うのは下記ぐらいだと思われる。

SkuPartNumberライセンス名ENTERPRISEPACKのような表記
ConsumedUnits使用ライセンス数
PrepaidUnits.Enabled購入数
主なプロパティ

この情報が取れれば、どのライセンスを何ライセンス契約していて、使用数が何ライセンスか分かり、計算すれば割当可能数も取得できるので、結構便利。

※SkuPartNumberはここに一覧がある。

以下実行例

GraphServiceClient cli = new GraphServiceClient(cred);
var lic = await cli.SubscribedSkus.GetAsync();
if (lic == null) {
    Console.WriteLine("Get License Fail");
    return;
}
if (lic.Value == null) {
    Console.WriteLine("Get License Fail");
    return;
}
foreach(var itm in lic.Value) {
    int all = (int)itm.PrepaidUnits.Enabled!;
    int use = (int)itm.ConsumedUnits!;
    Console.WriteLine($"{itm.SkuPartNumber},{all},{use},{all-use}");
}
/* 実行例
STREAM,XXXX,YY,MMMM
Dynamics_365_Sales_Premium_Viral_Trial,XXXX,YY,ZZZZ
WINDOWS_STORE,XXXX,Y,ZZZZ
ENTERPRISEPACK,XX,YY,ZZ
FLOW_FREE,XXXX,YY,ZZZZ
MICROSOFT_BUSINESS_CENTER,XXXX,Y,ZZZZ
POWERAPPS_VIRAL,XXXX,Y,ZZZZ
EXCHANGESTANDARD,XXX,YY,ZZ
POWER_BI_STANDARD,XXXX,Y,ZZZZ
OFFICESUBSCRIPTION,XXX,YYY,ZZ
Power_Pages_vTrial_for_Makers,XXXX,Y,ZZZZ
STANDARDPACK,XXX,YYY,ZZ
*/
カテゴリー: .NET, C#, Microsoft Graph, 技術系 | コメントする

Javascriptを使用して数値を通貨フォーマット化

Webアプリを作っていると、UIの作りが面倒な事がままある。UIライブラリ等を使用すれば良いのだろうが、調べたり、慣れたりするまでには意外と手間が掛かってしまう。

今回はあえてJavascript標準ライブラリのみを使用して、入力した数字を通貨フォーマットに変換してみたいと思う。

試してみるのは、<input type=”number”/>のフィールドに入力中に、横に千円単位で区切られた文字列を表示すること。

ちょっとググって(と言っても、bingなのだが・・・)みると、いくつかの方法が出て来た。その中で、1番使い勝手が良さそうなものをチョイスした。

以下のような感じで入力値を通貨形式に変換できるようだ。

var formattedval = Intl.NumberFormat('ja-JP', {style: 'currency', currency: 'JPY', minimumFractionDigits: 0}).format(val);

パラメータにはロケールと変換オプションを指定する。
ロケールはja-JPやen-US等の文字列。
オプションは

  • style
    • ‘decimal’ 10進数
    • ‘currency’ 通貨表示
    • ‘percent’ パーセント
  • currency 通貨の種類’JPY’,’USD’など
  • minimumFractionDigits 小数点以下の表示桁数

これを使用して、入力した値をリアルタイムで表示するようにコードを書いてみた。

<form>
    金額:
    <input type="number" style="text-align:right" id="Price"/>  
    <span style="font-weight:bold" id="FormattedPrice"></span><br/>
</form>
<script>
    $(document).ready(
        ()=>{
            $('#Price').on("input",
                (e)=>{
                    if (e.target.value.length != 0) {
                        var val = parseInt(e.target.value);
                        var formattedval = Intl.NumberFormat('ja-JP', {style: 'currency', currency: 'JPY', minimumFractionDigits: 0}).format(val);Intl.NumberFormat('ja-JP', {style: 'decimal', minimumFractionDigits: 0}).format(val);
                        $('#FormattedPrice').text(formattedval);
                    } else {
                        $('#FormattedPrice').text('');
                    }
                }
            );
        }
    );
</script>

実行結果はこんな感じ

何かに使えそうなのでメモ。

カテゴリー: javascript, Web, 技術系 | コメントする

blazor oninputイベント

blazorで<input …>コントロールの内容が変更された時のイベントとして、onchangeとoninputがあるが、onchangeはコントロールから、フォーカスが外れたときに発生するため、文字の入力中や変更中の値を取得することができない。

これに対して、oninputイベントは文字や値が変更されると即時に発生するため、入力中の文字や変更中の値をリアルタイムに取得することができる。

以下サンプル

@page "/change"

<table>
    <tr>
        <td>onchange:</td><td><input type="text" @onchange="TextChanged"/></td>
    </tr>
    <tr>
        <td>oninput:</td><td><input type="text" @oninput="TextChanged"/></td>
    </tr>
</table>
<br/>
Input Text:@Message

@code {
    protected string? Message;
    protected void TextChanged(ChangeEventArgs e) {
        Message = e.Value?.ToString();
    }
}

忘れない様にメモ。

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

列挙体メンバーを文字列として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#, 技術系 | コメントする