INumber<T>インターフェース

.NET7から数値系の型はすべてSystem.Numerics.INumber<T>インターフェースを実装することとなった。


INumber<T>には数値への四則演算やその他基本的な演算が含まれているので、数値系のジェネリクスクラスが作成しやすくなっている。

簡単な例を以下に挙げる。

using System.Numerics;
public class NumberList<T> where T :INumber<T> {
	protected List<T> Numbers { get; set; }
	public  NumberList() {
		Numbers = new ();
	}
	public NumberList(IEnumerable<T> Values):
		this() {
		Numbers.AddRange(Values);
	}
	public void Append(T Value) {
		Numbers.Add(Value);
	}
	public T Sum() {
		T sum = T.Zero;
		foreach(var v in Numbers) {
			sum += v;
		}
		return sum;
	}
	public void Map(Func<T,T> Operation) {
		for(int i=0; i < Numbers.Count; i++) {
			Numbers[i] = Operation(Numbers[i]);
		}
	}
	public IEnumerator<T> GetEnumerator() {
		foreach(var v in Numbers) {
			yield return v;
		}
	}
}
List<int> inumbers = new () { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
NumberList<int> ilst = new (inumbers);
Console.WriteLine(ilst.Sum());
ilst.Map((v)=>v*v);
foreach(var v in ilst) {
	Console.Write($"{v},");
}
Console.WriteLine();
List<double> dnumbers = new () { 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 };
NumberList<double> dlst = new (dnumbers);
Console.WriteLine(dlst.Sum());
dlst.Map(Math.Sqrt);
foreach(var v in dlst) {
	Console.Write($"{v.ToString("0.0000")},");
}
Console.WriteLine();

結果はこんな感じ

55
1,4,9,16,25,36,49,64,81,100,
5.5
0.32,0.45,0.55,0.63,0.71,0.77,0.84,0.89,0.95,1.00,
カテゴリー: .NET, C#, 技術系 | コメントする

.net7.0 C#11

ついに.net7.0が正式リリース。C#11も正式にサポートされるようになった。

.net7.0+C#11には色々な機能追加があるが、私が1番よく使うであろう物は「生文字リテラル」かな。MSのサイトでは「未加工の文字リテラル」と翻訳されているが、要は文字列中に「”」や「{」等をエスケープ文字無しで書ける構文だ。

以下のような感じで文字列を定義できる。

string jsonstr = """
[
    {"Name": "T.Sumomo", "Email": "t.sumomo@momo.com"},
    {"Name": "J.Sumomo", "Email": "j.sumomo@momo.com"},
    {"Name": "S.Sumomo", "Email": "s.sumomo@momo.com"}
]
""";

文字列補間も可能で、以下のような感じ

int x = 100;
int y = 200;
string replstr = $$"""
    { "X" : {{x}}, "Y": {{y}} }
""";

文字列補間と認識させる「{」の数分、「$」を付ける。

ちなみに、「”」の数も同じような感じで、「”””」までエスケープ無しならば、「””””」とする。

string example = """"
string jsonstr = """
[
    {"Name": "T.Sumomo", "Email": "t.sumomo@momo.com"},
    {"Name": "J.Sumomo", "Email": "j.sumomo@momo.com"},
    {"Name": "S.Sumomo", "Email": "s.sumomo@momo.com"}
]
""";
"""";

上記一応、動作確認してます。

後、別記事にするつもりだけど、数値系がINumber<T>をインプリメントしたこと。これにより、intやdouble等数値専用のジェネリッククラスの定義が簡単になった。それと、Int128かな。

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

MailKit SaslMechanismOAuth2

前のブログ記事のコメントに、MailKitの「SaslMechanism(OAuth2)は使い回しができないようだ。」と書いたけど、Resetメソッドを使うと、問題無く動きました。このメソッドが何をやっているかというと、IsAuthenticateプロパティをfalseに設定しているだけなんだけどね。(ベースクラスで実装されてた)

var cred = new UsernamePasswordCredential(PopUser,PopPass,Authority,AppID,options); 
// Scopesは["https://outlook.office365.com/POP.AccessAsUser.All","https://outlook.office365.com/SMTP.Send"]
var req = new TokenRequestContext(scopes);
var token = await cred.GetTokenAsync(req);
var oauth2 = new SaslMechanismOAuth2(PopUser, token.Token);

using (Pop3Client cli = new Pop3Client()){
    await cli.ConnectAsync("outlook.office365.com", 995, SecureSocketOptions.SslOnConnect);
    // AccessTokenで認証
    await cli.AuthenticateAsync(oauth2);
    int cnt = await cli.GetMessageCountAsync();
	・・・
}
using (SmtpClient scli = new SmtpClient()) {
    MimeMessage msg = new MimeMessage();
	・・・
    // SASLの認証済みフラグを落とす
    oauth2.Reset();

    await scli.ConnectAsync("outlook.office365.com", 587, SecureSocketOptions.StartTls);
    await scli.AuthenticateAsync(oauth2);
    await scli.SendAsync(msg);
    await scli.DisconnectAsync(true);
}

もちろん、長時間使用していない場合は、AccessTokenがExpireしてしまうので、その場合はAcessTokenの取得から行なわなければならないけど・・・

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

Azure.Identity+MailKitでOAuth2 POP3

Microsoftは、SMTPやPOP3,IMAPの基本認証プロトコルでのアクセス許可を9月30日よりサイト毎に順次停止していく予定だ。

これにより、基本認証を使用してPOP3やIMAPなどでメールを受信するプログラムが動作しなくなる可能性が高くなる。

これは私も承知していて、MailKitのSaslMechanismOAuth2クラスを使用してOAuth2対応にするよう修正していたのだが、その際に使用したMicrosoft.Identity.ClientMicrosoft.Graph.Authライブラリが以前書いたように、非推奨となってしまったので、Azure.Identityに変更してみた。

基本はMicrosoft Graphを使う場合と同じなのだが、注意点として、ユーザーやグループを捜査するMS GraphではスコープのPrefixが必要では無かったのだが、POP3やIMAP,SMTP等では、”https://outlook.office365.com/”のPrefix(ネームスペースなのかな?)が必要となる事。

POP3の場合は以下のようなコードとなる。

・・・
using MimeKit;
using MailKit;
using MailKit.Net.Pop3;
using MailKit.Security;
using MimeKit.Text;

using Azure.Core;
using Azure.Identity;
・・・
// この例では、Resource Owner Password Credentialsフローを使用

// スコープ
string[] scopes = new string[] { "https://outlook.office365.com/POP.AccessAsUser.All" };

var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

// ユーザー名,パスワード,テナントID,アプリケーションID(Client ID)から資格情報を得る
var cred = new UsernamePasswordCredential(PopUser,PopPass,Authority,AppID,options); 

// スコープを指定してAccess Tokenの取得
var req = new TokenRequestContext(scopes);
var token = await cred.GetTokenAsync(req);

// ユーザー名とAccess Tokenを使用してOAuth2用の認証機構を作成
var oauth2 = new SaslMechanismOAuth2(PopUser, token.Token);

using (Pop3Client cli = new Pop3Client())
{
    await cli.ConnectAsync("outlook.office365.com", 995, SecureSocketOptions.SslOnConnect);
    // OAuth2で認証
    await cli.AuthenticateAsync(oauth2);
	・・・

最初、Authentication Failが発生したので、アカウントを変えたり、色々してみたが、全然だめなので、もしやと思い、スコープにPrefixを付けたら、うまく動作した。

OAuth2対応についてはネットで調べても、Microsoft.Identity.Clientを使用した例ばかりで、解決に結構時間が掛かってしまった。

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

blazorでファイルダウンロード

blazorにはwebappのFileResultを使用したPostBackような動的にファイルコンテンツを作成して、ファイルをダウンロードさせる仕組みが無い。

では、どうやってファイルコンテンツを作成して、ダウンロードさせるかを調べてみた。

普通に考えると、1番簡単なのは、コールバックでコンテンツを作成して、Data URLに変換し、そのリンクを表示させる方法だが、これだと、作成のトリガーとリンクをクリックするという、2アクションが必要になる。

結局、作成トリガー(例えばボタンクリック)でファイルのダウンロードまで、行うには、Javascriptの助けを借りることになる。

以下のような感じで可能だが、サイズ制限(MSのサイトでは250Mbytes以下と記述されていた)があるようだ。

//
//  ファイルをダウンロードするJSヘルパー
//      やっていることは単純で、StreamからBlobを作成してそのurlをもった、
//      aタグを生成し、aタグに対してclickイベントを発生させた後、aタグとdata url
//      に割いているリソースを開放。
//      結構良く使う手段だね。blazor独自の点は.NETのStreamを
//      Javascript用にWrappingしてarrayBufferを取得している点ぐらい。
var mylibrary = {
    DownloadFile : async (StreamRef, FileName) => {
        const arrayBuffer = await StreamRef.arrayBuffer();
        const blob = new Blob([arrayBuffer]);
        const url = URL.createObjectURL(blob);
        const anchorElement = document.createElement('a');
        anchorElement.href = url;
        anchorElement.download = FileName ?? '';
        anchorElement.click();
        anchorElement.remove();
        URL.revokeObjectURL(url);
    }
};
using(MemoryStream mstm = new MemoryStream()) {
	// コンテンツ作成
	await mstm.FlushAsync();
	// 念のため
	mstm.Seek(0L,SeekOrigin.Begin);
	// Javascriptに渡すためのStreamリファレンス作成
	using var stmRef = new DotNetStreamReference(stream: mstm);
	// Javascript呼出
	await JSRuntime.InvokeVoidAsync("mylibrary.DownloadFile",stmRef,FileName);
}

大きいファイルの場合は、物理的にファイルを作成して、そのリンクでaタグ作成および、クリック・・・という形を取れとのこと。

何か他に良い方法は無いものだろうか・・・

ファイルダウンロードの標準的な仕組みをblazor内で定義して欲しいものだ。

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

ClosedXML

.NET上からExcelファイルを操作するのに、私は今まで、epplusを使用してきたが、ご存じの通り、epplus(5.x以降)が有償化されてしまってからかなり時間が経つ。

仕方なくepplusの古いバージョン(4.x)を使っていたけれど、何か他に代わりになる物がないのか探してみた。

poiの.NETポーティングである、npoiが有ることも知っていたが、こちらは少々使い勝手が悪いので不採用。

epplusのような使い勝手のものを探していたら、ClosedXMLというオープンソースのライブラリパッケージを見つけた。(私が知らないだけで、結構有名らしい。)

ClosedXMLはnugetからパッケージ取得が可能。

こちらのライブラリは、npoiよりもepplus寄りの使い勝手で、中々良さそうだ。ただ、バージョンが、最新でも、0.96.0で、1.0以下なので、その辺はどうなのだろうか・・・

チョット使ってみた感じでは特に動作に問題は無さそうだ。ただ、きちんと測っていないが、パフォーマンスがepplusより悪い感じがする。

まぁ、簡単なExcelファイルを作るくらいなら問題は無さそうだが・・・

下記が、簡単なサンプル。npoiより使い勝手いいでしょ?

using(var wb = new XLWorkbook()) {
    var ws = wb.Worksheets.Add("MySheet");
    // セル結合
    ws.Range(1,1,1,10).Merge();
    ws.Cell(1,1).Value = "ClosedXML Test";
    // 文字配置
    ws.Cell(1,1).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);

    for(int i=1; i <= 10; i++) {
        for(int j=1; j <= 10; j++) {
            ws.Cell(i+2,j).Value = i*j;
        }
        var xrng = ws.Range(i+2,1,i+2,10);
        if ((i%2) == 0) {
            // セル背景色
            xrng.Style.Fill.SetPatternType(XLFillPatternValues.Solid);
            xrng.Style.Fill.SetBackgroundColor(XLColor.AirForceBlue);
        }
    }

    for(int i=0; i < 10; i++) {
        // Excel関数
        char c = (char)('A'+i);
        ws.Cell(13,i+1).SetFormulaA1($"=SUM({c}3:{c}12)");
    }

    // 罫線
    var rng = ws.Range(3,1,13,10);
    rng.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
    rng.Style.Border.SetInsideBorder(XLBorderStyleValues.Thin);

    // 幅自動調整
    ws.Columns(1,10).AdjustToContents();

	// 保存
    wb.SaveAs(filename);
}
結果

SaveAsメソッドはStreamに書き出すことも可能なので、WebAppでは、MemoryStreamに書き出して、FileResultを返すことによって作成したファイルのダウンロードも可能。

public async Task<IActionResult> OnPostAsync() {
	・・・
	// ダウンロード
	using(MemoryStream stm = new MemoryStream()) {
	    wb.SaveAs(stm);
	    await stm.FlushAsync();
	    byte[] buf = stm.ToArray();
	    return File(buf,"application/octet-stream",FileName);
	}
}

Workbook(XLWorkbook)のコンストラクタに、ファイル名やStreamを指定することで、Excelファイルを読み込めるので、テンプレート的な使い方も可能。

チョット使い込んでみようかな。

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

node.jsでMongoDb GridFSへファイルアップロード

前回は、ファイルのダウンロードだったけど、今回はアップロード。

アップロードの場合、フォームをpostするのに、enctype=’multipart/form-data’とする必要があるが、node.js初心者のため、まずはここの処理でつまずいた。

ASP.netなんかだと、IFormFileなどで、簡単にファイルを切り出せるけど、node.jsではサードパーティ製のフィルタ(?)を使用する必要があるようだ。一番一般的に使われているのはmulterというものらしい。post時にこのフィルタを指定すると、ファイル以外のフィールド値とファイルを取得できる。

<form method="post" enctype="multipart/form-data">
	タイトル:<input type="text" name="title"/>
	ファイル:<input type="file" name="upfile"/>
	<input type="submit" value="アップロード"/>
</form>
var router = express.Router();
var multer = require('multer');
var upload = new multer();
var mongo = require('mongodb');
var buffer = require('buffer');

router.post('/', upload.any(), async (req,res,next) => {

	// fieldとファイルを取得
	const { fld , files } = req;

	var title = fld['title'];
	var upfile = files[0];

	・・・

ちなみに、upfile(=files[0])の内容は以下のような感じ。

{
  fieldname: 'upfile',
  originalname: 'ファイル名.タイプ',
  encoding: '7bit',
  mimetype: 'image/png',
  buffer: <Buffer 89 50 4e  ... 5926 more bytes>,
  size: 5976
}

これをMongoDbのGridFSにアップロードしたいのだが、やはりダウンロードと同じく、非同期しかサポートされていないので、以下のような関数を定義した。

	await cli.connect();
	let db = cli.db('DBName');
	let fs = new mongo.GridFSBucket(db);
	let stm = fs.openUploadStream(upfile.originalname);
	let fileid = await WriteAsync(stm,upfile.buffer);
・・・
//
//  GridFSへの書き込み(同期っぽく見えるようにしてみた)
//
async function WriteAsync(stm,data) {
  return new Promise((resolve,reject) => {
    try {
      // end()を呼ぶとfinishイベントが発生
      stm.on('finish',()=>{
        resolve(stm.id);
      });
      stm.on('error',()=>{
        reject('Write Error');
      });
      var sts = stm.write(data);
      if (!sts) {
        reject('Write Error');
      }
      // これ呼ばないと永遠に終わらない
      stm.end();
    } catch (err) {
      reject(err);
    }
  });

気を付けないといけないのは、GridFSBucketWriteStream.end()を呼び出さないと、永遠に待ち状態になってしまうところ。

色々と調べたのだが、あまり良い例が載っていなくて苦労した。

カテゴリー: MongoDB, node.js, Web, 技術系 | コメントする

node.jsでMongoDB GridFSからファイル取得

node.jsでMongoDB上のGridFSからファイルをダウンロードするのに手間取ったので、メモ。

やりたかったのは、MongoDB上のGridFSからイメージファイルデータを取得して、データURLを作りたかっただけだけど、node.jsのMongoDB DriverのGridFSBucketReadStreamのreadメソッドが、非同期動作しかできないようで、色々苦労した・・・

結局、read周辺の部分をpromiseを使用して、async funcitonにする事でとりあえず、一見落着。

var mongo = require('mongodb');
var buffer = require('buffer');
・・・
let fs = new mongo.GridFSBucket(db);
// ObjectIdを使用してGridFSBucketReadStreamを得る
var stm = fs.openDownloadStream(ObjectId("xxx・・・"));
// 同期的にイメージファイルデータ取得
var body = await ReadAsync(stm);
var imgUrl =  'data:image/jpeg;base64,' + buffer.Buffer.from(body).toString('base64');

//
// GridFSBucketReadStreamから同期的に(見えるように)データ取得
//	戻り値:Promise<Buffer>
//
async function ReadAsync(stm) {
  return new Promise((resolve,reject) => {
    try {
      var totalsize = 0; // 読み取ったトータルサイズ
      var chunks = [];  // 読み取ったchunk
      // データ取得時コールバック定義
      stm.on('data',
        (v) => {
          // 読込んだデータ(chunk)を保存
          var buf = buffer.Buffer.from(v);
          chunks.push(buf);
          totalsize += buf.length;
        }
      );
      // データ終了時コールバック定義
      stm.on('end',
        () => {
          // 結合先のBufferを作成
          var ximage = buffer.Buffer.alloc(totalsize);
          // 全chunkの結合
          var offset = 0;
          for(var i=0; i < chunks.length; i++) {
            chunks[i].copy(ximage,offset);
            offset += chunks[i].length;
          }
          // 読込んだデータを返す
          resolve(ximage);
        }
      );
      // 読込可時コールバック定義
      stm.on('readable',
        // 読込開始
        ()=> stm.read()
      );
    } catch (err) {
      reject(err);
    }
  });
}

解決するまでにちょっと時間が掛かったなぁ・・・

これが正解かどうかは分からないけど、取敢えず動いているからいいや。

正規のライブラリにもawait stm.read()で呼び出させるようなメソッド用意しておいて欲しいな。

カテゴリー: MongoDB, node.js, 技術系 | コメントする

GroupByのグルーピング対象を動的に変更する

.NET LinqでGroupByを使用して、クエリ内容をグルーピングする際に、場合によって、グルーピング対象を動的に変更したい場合がある。

これを実装するのに一番簡単な方法を見つけたのでメモ。

Enumerable.GroupBy拡張メソッドのオーバーロードの1つに、以下の物がある。

GroupBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IEqualityComparer<TKey>)

このオーバーロードのパラメータ、IEqualityComparer<TKey>をインプリメントしたクラスを作ることで、グルーピング単位を外部から指定できるようになる。

例えば、以下のようなクラスのリストを「年」+「月」でグルーピングしたり、「年」+「月」+「セールスマン」でグルーピングしたりしたい場合、

public partial class Sales
{
    public int Year { get; set; }            // 年
    public int Month { get; set; }           // 月
    public string SalesMan { get; set; }     // セールスマン
    public string ProductName { get; set; }  // 商品名
    public long Qty { get; set; }            // 販売数
}

IEqualityComparer<Sales>をインプリメントして、以下のようなクラスを作る。

class SalesEqaulityComparer : IEqualityComparer<Sales> {
    public bool useYear { get; set; }
    public bool useMonth { get; set; }
    public bool useSalesMan { get; set; }
    public bool useProductName { get; set; }
    // 比較方法(IEqualityComparer<T>)
    public bool Equals(Sales x, Sales y) {
        if (useYear && !x.Year.Equals(y.Year)) return false;
        if (useMonth && !x.Month.Equals(y.Month)) return false;
        if (useSalesMan && !x.SalesMan.Equals(y.SalesMan)) return false;
        if (useProductName && !x.ProductName.Equals(y.ProducName)) return false;
        return true;
    }
    // HashCodeの取得(IEqualityComparer<T>)
    public int GetHashCode(Sales s) {
        int hash = 0;
        if (useYear) hash ^= s.Year.GetHashCode();
        if (useMonth) hash ^= s.Month.GetHashCode();
        if (useSalesMan) hash ^= s.SalesMan.GetHashCode();
        if (useProductName) hash ^= s.ProductName.GetHashCode();
        return hash;
    }
}

実際にグルーピングするときは、このクラスインスタンスを作成し、グルーピングしたいuseXXXにtrueを設定することにより、指定した項目でグルーピングができる。

例えば、「年」,「月」でグルーピングしたい場合は、useYearとuseMonthにtrueを設定し、その他はfalseに設定。「年」,「月」,「セールスマン」でグルーピングしたい場合は、useYear,useMonth,useSalesManにtrueを設定して、その他はfalseを設定して、以下のようなクエリを発行する。

List<Sales> SalesList = new SalesList();
・・・
var salescomparer = new SalesEqaulityComparer();
// グルーピングしたい項目の指定
salescompare.useYear = true;
salescompare.useMonth = true;
・・・
var g = SalesList.GroupBy(s=>s,salescomparer);
foreach(var itm in g) {
	Console.WriteLine($"{itm.First().Year},{itm.First().Month},・・・{itm.Sum(v=>v.Qty)}}");
}

意外と使えます。

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

またまた、deprecateパッケージ発見してしまった・・・

私が知らなかっただけなのかもしれないが、Microsoft.EntityFrameworkCore.SqlServer.DesignやMicrosoft.EntityFrameworkCore.Sqlite.Design等のパッケージがdeprecate対象となっていた。

今まで、SQL ServerやSqlite等をEF Coreを使用してscaffoldする場合やmigrationするのに、無条件でこれらのパッケージ追加していたんだが、いつの間にやら、このパッケージ無しでもscaffoldやmigrationできるようになっていたみたいだね・・・

Pomelo.EntityFrameworkCore.MySql.Designとかはnuget.orgで見る限りではdeprecate対象にはなっていないようだが、更新日が2017年と古い。試してはいないけど、DBプロバイダ.Designパッケージは必要無くなったのかな・・・

カテゴリー: .NET, Entity Framework, 技術系 | 2件のコメント