dotnet core系からのSOAPアクセス

SOAPを使用したWeb APIって、最近はあまり見かけないけど、まぁ、残っているところには残っていて、それを使いたかったので、dotnet core系から使うにはどうすれば良いかを調べてみた。

.NET Frameworkの頃はVisual Studioを使って、サービス参照から追加すれば簡単にできたのだが、dotnet core系でCLI環境ではそういうわけにはいかない。で、調べてみたら、svcutilを使って、WSDLからアクセス用のクラスとデータクラスを自動生成してくれるようだ。svcutilは.NET Frameworkからあったけど、dotnet core系の場合、toolとして、dotnet-svcutilが提供されている。まず、このツールをインストールしよう。

dotnet tool install dotnet-svcutil -g

で、以下のようにして、WSDLからアクセス用とデータ用のクラスを生成する。

dotnet svcutil <WSDLのURL>

オプションを何も指定しないと、実行したフォルダの下にServiceReferenceと言うフォルダが作成され、namespace ServiceReference中にアクセス用のクラスとデータクラスが作成される。オプション指定でフォルダ名やnamespaceの指定も可能。

プログラムから、サービスにアクセスするためには、プロジェクトに下記のパッケージを追加する必要がある。

  • System.ServiceModel.Primitives
  • System.ServiceModel.Http

プログラム中からSOAPサービスに接続するには、以下のようにサービスアクセス用のインスタンスを作成してメソッドを呼び出す。(認証等を使用しなければならない場合はbindingや認証設定などが必要)

using System.ServiceModel;
using ServiceReference; // svcutilでnamespaceを指定した場合はそのnamespace
・・・
// サービスアクセス用インスタンスの作成
var cli = new <サービス名>Client();
// サービスのメソッドを呼び出す
var result = await cli.<メソッド名>Async(<パラメータ>);

using System.ServiceModel;
using ServiceReference; // svcutilでnamespaceを指定した場合はそのnamespace
・・・
// GetUserService SOAPクライアント
// ※認証なし
var cli = new GetUserServiceClient();

// ユーザー番号00001番のユーザーを取得
// ※Userクラス定義はWSDLから自動的に生成される
User ret = await cli.GetUserAsync("00001");

以上、忘れないようにメモ。

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

LINQでLEFT OUTER JOIN

LINQで外部結合(LEFT OUTER JOIN)を行うためのメモ。

LINQで内部結合(LEFT INNER JOIN)を行うには、JOIN句を使って、以下のような感じで行えば良いのは良く例も出ているし、構文上も素直に納得できる。
(ちなみに、複数キーがある場合は、
new {t1.key1,t1.key2[,…]} equals new {t2.key1,t2.key2[,…]}
の形で指定する)

// Collection1とCollection2の[KEY]が同じものを結合
// 同じキーを含まないCollection1は集合から外される。
var q = from t1 in Collection1
	join t2 in Collection2 on t2.[KEY] equals t1.[KEY]
	・・・

では、外部結合(LEFT OUTER JOIN)、つまり、Collection1は全てのレコードが選択され、Collection2が存在しない場合は、t2をNULLとする)にはどのように表現するのが一番簡単か?

答えは意外なもので、以下のようなクエリを用いることでかなり簡単に実装可能である。

// Collection1とCollection2の[KEY]が同じものを結合
// ただし、Collection2と同じキーを持っていないCollection1も集合に含める
var q = from t1 in Collection1
		from t2 in Collection2.Where(c=>c.[KEY] == t1.[KEY]).DefaultIfEmpty()
	・・・

1つのクエリ中にfrom句を2回使用して、2つ目の集合は1つめのキーで絞込をかけ、さらに、そのキーを持つレコードが存在しない場合にはデフォルト値(null)を返すようにする。

つい最近まで、from句を2回以上、使用できるの知らなかった・・・

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

MBRからGPTへの変換

今秋位にWindows11がでるらしいので、自宅のPCでWindows11が動作するか、MS.謹製のツールで確認してみたところ、NGとなった。

4年前ぐらいに作ったPCで、Intelの第8世代Core i7だったので、行けると思っていたのだが・・・

MS.謹製のツールでは何が悪いか細かく出てこないので、このツールを使ってチェックしてみたところ、BootがBIOSモードでGPTのボリュームが存在していないと表示された。(それ以外はOK)

原因はBIOS設定のCSM有効のまま、Windowsをインストールしたためのようだ。デフォルトがそうなっていたかどうかは忘れたが、そのせいでWindowsはMBRモードでインストールされていた。

それじゃぁ、MRRをGPTに変換してみようということで、変換にチャレンジ。

まずは、EaseUS(商用版)でチャレンジ。PEイメージを作成して、DVDからブート後、イメージバックアップを取ってから、GPTへの変換を行ってみた。途中まではうまく変換されているようだが、「不明なエラー」が表示されてNG。

仕方ないので、他の方法を探してみる。

ググってみると、mbr2gptというMS謹製のコマンドがあるらしい。

まず、GPTに変換できるかをチェックする。

> mbr2gpt /validate /disk:1 /allowFullOS
MBR2GPT: Attempting to validate disk 1
MBR2GPT: Retrieving layout of disk
MBR2GPT: Validating layout, disk sector size is: 512 bytes
MBR2GPT: Validation completed successfully
>

/disk:n でディスク番号を指定する(上記はバックアップした媒体に対してチェックしてみた)。

最後に「MBR2GPT: Validation completed successfully」と表示されたら、変換可能。

で、バックアップしてあるので、早速変換をかけてみた。

> mbr2gpt /convert /disk:0 /allowFullOS

すると、以下のようなエラーが・・・

MBR2GPT: Failed to update ReAgent.xml, please try to manually disable and enable WinRE

で、またまたググってみると、こんな動画が見つかった(英語です)。

見進めていくと、同じエラーが出た後、リブートして、BIOS設定でCSMを外してUEFIブートに変更(こればかりはBIOSメーカーによって違うから、自分で項目を探さないとだめだが)後、保存してWindowsを起動する。

ちゃんと再起動ができれば、GPTへの変換は完了している。

もしかしたら、EaseUSのエラーもこれだったのかも。動いているから、もう一度確かめるつもりはないが、他のPCで試してみようか。

で、オープンソースの互換性チェックをかけたら、ALL Green。ついでに、MS.謹製のツールでもチェックしてみたら、Windows11にアップグレード可能と出ましたとさ。

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

ラズパイの開発環境

ラズパイで遊んでいるのだが、開発環境についてちょっと書いておこう。

ラズパイ本体は3B+で、ケースなどのキットとして購入。OSはこのキットに付いてきた、MicroSDに入っていたデフォルトのRaspberry Pi OS(32bit)。

最初だけ、HDMIでディスプレイとつなげ、USBキーボードとUSBマウス繋いで、無線LANのセットアップを行った。(GUIで超簡単にできたよ。)

無線LANで固定IPにした後は、WindowsからSSHで接続し、TeratermやWindows Terminal等で操作。

開発用として.net 5.0 SDKのARM32バイナリ版とVScodeを導入。

開発は、dotnet cliでプロジェクトを作成した後、Windows上のVSCodeのRemote-SSH Extensionを使用して、フォルダを開きコードをエディットと言う形で行っている。

ラズパイ3B+はメモリが1G Byteしかないので、ラズパイ上のGUIで行うよりこちらの方が、速いと思う。

ちなみに、C# Extension(OmniSharpベース)はこのバージョンのArm版Linuxに対応していないので、デバッグするにはリモートでバッグ手法をとらなければならない。(それほど複雑なプログラムを組むわけじゃないからデバッガ使わないけど・・・)

メモリ1G Byteなので、毎回ビルドすると、それなりに時間がかかるので、作成したプログラムを動作させる場合は、dotnet buildでビルドしておいてから、実際に動かすときは、dotnet run –no-buildオプションを付けて再ビルドしないで、実行させている。

なお、電子回路の動作はConsoleプロジェクトを作って確認し、確認後に、Blazor ServerでUIを持つアプリを作って動かしている。

とまぁ、こんな感じです。

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

ラズパイ+.net 5でIOTもどき

ついに、ラズパイに手を出してしまった・・・

ちょっと、マシンルームの温度計測やメールによるアラート機能を付けたくて、とりあえず、ラズパイ(3B+)と入門キットを購入。

電子回路なんて、高校の時にイジって以来だから、全然分からないけど、とりあえず、入門書とか読みながら、簡単な回路を作ってお試し中。

入門キットのサンプルは、CやPythonなんかで書かれているけど、やはり、ここはC#で書きたいよね。

で、ラズパイに.net 5を導入。ラズパイ3B+はARM64なんだけど、デフォルトで入っている、OSは32bit(メモリ1Gだからまぁ妥当だろう)のDebianベースのOSだが、デフォルトではapt listで調べるも、dotnet-sdkは見つからない。何かしらの方法はあるのだろうが、パッケージマネージャーからインストールするのは諦めて、.net 5ダウンロードサイトからのarm32版のtar.gzを落としてきて、展開。(どうせ、複数人数で使うわけじゃないから問題無し)

で、簡単な回路を作成してみる。

回路

とりあえず、赤枠で囲んだ部分が最初に作った回路。簡単に書くと以下のような回路になっている。

青いケーブルが+3.3Vに刺さっていて、赤いケーブルはラズパイ上のGPIO0に刺さっている。GPIO0の後の()に17と書かれているのはBCMと呼ばれる番号で、Microsoft製のライブラリはこちらの番号を指定する必要がある。以下はgpioコマンドの出力で、いくつかの名前やピン番号が表示されている。ライブラリによって、使用するピン番号が違うので、注意されたい。

ラズパイGPIO

なんで複数の呼び方があるのかとか言うのは、ラズパイ入門編のサイトとかに必ず書いてあるので、そちらを参照して欲しい。

で、今回この回路でやりたいのは、LEDの点灯・消灯である。回路を見ると、GPIO0(BCMピン番号17)が邪魔をする形になっているので、LEDを点灯させたければ、GPIO0(BCMピン番号17)の電位を落とす必要がある。逆に消灯させたい場合は電位を上げれば良い。

ということで、以下のようなコードでプログラムからLEDのオン・オフが実現できる。
(プロジェクトに↓のパッケージ追加が必要)

  • System.Device.Gpio
  • Iot.Device.Bindings

プログラムは単純にConsoleアプリで作ってみた。

using System;
using System.Threading;
// GPIOライブラリ
using System.Device.Gpio;

namespace oneLedCtrl
{
    class Program
    {
        static void Main(string[] args)
        {
            const int pinno = 17;   // BCM Pin No
            // GPIOコントローラインスタンス作成
            var ctrl = new GpioController();
            // Pinのオープン(書き込みモード)
            ctrl.OpenPin(pinno,PinMode.Output);
            PinValue val = PinValue.High;
            // 1秒ごとに点滅を繰り返す
            for(;!Console.KeyAvailable;) {
                if (val == PinValue.High) {
                    val = PinValue.Low; // 点灯
                } else {
                    val = PinValue.High; // 消灯
                }
                // GPIO0(BCM 17)に書き込み
                ctrl.Write(pinno,val);
                Thread.Sleep(1000);
            }
            ctrl.ClosePin(pinno);
        }
    }
}

これくらいの回路なら、簡単だからいいんだけど、複雑なのは配線も面倒だよね。
業務で使うのなら、やっぱり、回路が組んである製品を購入すべきなんだろうね。

まぁ、とりあえずは、入門キットで色々と遊んでみるつもり。

カテゴリー: C#, IOT, raspberry pi, 技術系 | 1件のコメント

知っている人には当たり前かもだけど・・・

asp.net core webappのページハンドラはデフォルトでは、GET要求はOnGet,POST要求はOnPostによって処理されるが、View側から実行するハンドラを変更することが可能。

方法は、TagHelperを使用して、Submit時にasp-page-handler属性を指定する。

例えば、以下のような感じで、Submitボタンによって呼ばれるハンドラを指定することが可能。

<form method="post">
	<button class="btn btn-secondary" asp-page-handler="cancel" type="submit">Cancel</button>  
	<button class="btn btn-primary" asp-page-handler="ok" type="submit">OK</button>
</form>

このような指定をすると、Cancelボタンクリック時には、OnPostCancelが呼び出され、OKボタンクリック時にはOnPostOkが呼び出される。ちなみに、ハンドラはOnの後ろに、asp-page-handlerで指定された名前の最初が大文字であれば、その後ろは大文字でも、小文字でも大丈夫なようだ。(OnPostOKでもOnPostOkでも大丈夫⇒静的言語なのに・・・)

なお、asp-page-handlerを指定した場合、URLに?handler=<ハンドラ名>が付加される。

URLを勝手に変更されたり、動的言語的な判断されるので、個人的には、好きな方法じゃないけど・・・

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

DBからScaffold(RazorPage)

Visual Studio ASP.net MVCには、DBをScaffoldしてCRUDのページを自動作成する機能があったが、dotnet core(.NET)でも、同等の機能があったので、メモ。

dotnetのCLIでDBをScaffoldしてCRUDページを作成するには、aspnet-codegeneratorツールのインストールが必要。

下記で、ツールをインストールしておく。

dotnet tool install dotnet-aspnet-codegenerator -g

ちなみに、このツールを動かすためには、プロジェクトにMicrosoft.EntityFramework.SQLServerかMicrosoft.EntityFramework.Sqliteパッケージの追加が必要。(ツールがMemory上にテンポラリのDBを作成して使用するようだ)

ツールを動作させる前にDbContextが必要になるので、DBのScaffolding等で作成しておく。

次に、下記コマンドを実行する。(Razorページの場合の例。ツール実行にSQLServerではなく、Sqliteを使用する場合は -sqliteも必要)

dotnet aspnet-codegenerator razorpage -m <モデル(エンティティ)名> -dc <コンテキスト名> -outDir Pages/<出力先ディレクトリ> -scripts

指定したフォルダにCURDページが作成される。ちなみに、指定しない場合、ネームスペースはディレクトリ名と同様となる。

作成されたページを表示させるためには、Startup.csのConfigureServicesメソッドに、下記のようにDbContextを追加する。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<Dbコンテキスト名>();
    services.AddRazorPages();
}

実際にページを表示してみると、以下のような感じになる。

一覧
Create
Edit
Delete
Detail

ちなみに、文字列の最大長や入力レンジ,入力必須などのチェックはモデルに属性を付けておけば、自動的に行ってくれる。

暫定的なメンテナンス画面が欲しい時などには使えるかな。

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

BlazorのForm入力値検証について

Blazorで、Formの入力値を検証する方法を調べてみた。Blazorでは基本的にFormをSubmitしないので、すっかり頭から抜けていたけど・・・

Submitと言っても、実際にSubmitを行う訳では無いようだが、入力値のチェックは行う事が可能。

まず、入力値を保存するために以下のようなクラスを定義する必要がある。

// ラジオボタン用のEnum
public enum Countries { Japan, USA, China, UK }
// 入力値の保存用クラス
public class FormItems {
    [Required(ErrorMessage = "氏名は必須入力です")]
    [StringLength(10,ErrorMessage = "氏名は10文字以内です")]
    public string Name { get; set; }
    [Required(ErrorMessage = "希望国は必須選択項目です。")]
    public Countries? Country { get; set; } = null;
    [Range(typeof(bool),"true","true",
        ErrorMessage = "同意が必要です。")]
    public bool IsOK { get; set; }
}

メンバーに属性を付けることにより、入力値の検証を行う事ができる。
例えば、[Require]属性を付けると、入力値が無い場合は検証エラーとなる。文字列などは[StringLength]属性を使用して、最大長などを指定できる。

フォーム入力用にOnInitialize等で、上記クラスのインスタンスを作成しておき、以下のようにEditFormのModelとしてインスタンスを指定し、各入力タグのBind先として、メンバーを指定する。(この例では、OnValidSubmit属性にDoSubmitメソッドを指定して、全検証OKの場合、DoSubmitメソッドが呼び出されるようにしている)

<!-- InputValuesはFormItemsのインスタンス-->
<EditForm Model="@InputValues" OnValidSubmit="DoSubmit">
    <DataAnnotationsValidator/>
    <table>
    <tr>
        <td>氏名:</td><td><InputText @bind-Value="InputValues.Name"/></td><td><ValidationMessage For="@(()=>InputValues.Name)" class="validation-message"/></td>
    </tr>
    <tr>
        <td colspan="2">希望国:</td><td><ValidationMessage For="@(()=>InputValues.Country)" class="validation-message"/></td>
    </tr>
    <InputRadioGroup @bind-Value="InputValues.Country">
        @foreach(var itm in Enum.GetValues(typeof(Countries))) {
            <tr>
                <td colspan="3"><InputRadio Value="itm"/>@itm</td>
            </tr>
        }
    </InputRadioGroup>
    <tr>
        <td colspan="2"><InputCheckbox @bind-Value="InputValues.IsOK"/>添付の条項に同意します。</td><td><ValidationMessage For="@(()=>InputValues.IsOK)" class="validation-message"/></td>
    </tr>
    </table>
    <br/>
    <button class="btn btn-primary" type="submit">送信</button>
</EditForm>

検証時のエラーはValidationMessageタグでそれぞれの項目に関連づけて表示させているが、ValidationSummaryタグで一括で表示させることも可能。InputRadioGroupタグはBlazor5.0から使用可能なタグで、チェックされたラジオボタンの値がBindしたメンバーに設定される。

実行例

といった感じで、Form入力値の検証が可能。他にも、数値や日付の範囲指定等がメンバー属性だけで検証可能。また、カスタム検証ロジックの適用も可能。

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

EF Core(SQL Server)のLINQクエリにContainsを使ってみた結果

EF CoreでSQLのINに相当する検索を行いたかったので、以下のようなLINQクエリを発行してみた。

// 検索対象の部署コード
string[] codes = new string[] { "XXX", "YYY", "ZZZ" };
// LINQクエリ(v.dcodeがcodesの中に含まれる)
var q = ctx.xxxTable.Where(v=>codes.Contains(v.dcode));

EFCoreのロギングレベルをInformationにして、生成されたSQLを確認したところ、

SELECT ・・・
	FROM [xxxTable] AS [x]
	WHERE ([x].[dcode] IN (N'XXX', N'YYY', N'ZZZ'))

と、見事にINを使用したSQLに変換されていた。

結構すごいと思わない?ちなみに、SQLiteでも同じようなクエリが発行されていた。

MS謹製以外のEFドライバはどうなんだろうか・・・

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

blazorからgeolocation APIを使ってみる

HTML5をサポートしているブラウザでは、GPS等を使用して、位置情報を得る、geolocation APIを使用することが可能である。

geolocation APIには以下のような関数が用意されている。

geolocation.getCurrentPosition-位置情報を1回だけ取得する。

//
//	loc:位置情報
//	err:エラー情報
//
navigator.geolocation.getCurrentPosition(
    function(loc) {}	// 位置情報取得成功時処理
    function(err) {}	// 位置情報取得失敗時処理
);

geolocation.geolocation.watchPosition-位置情報が変わるたびに指定関数を呼び出す。

//
//	loc:位置情報
//	err:エラー情報
//
watchid = navigator.geolocation.watchPosition(
    function(e) {},		// 位置情報取得成功時処理
    function(e) {}		// 位置情報取得失敗時処理
);

geolocation.clearWatch-watchPositionの停止

//
//	watchid:watchPositionの戻り値
//
navigator.geolocation.clearWatch(watchid);
位置情報パラメータ
coords 位置情報
latitude緯度
longitude経度
altitude高度
accuracy緯度・経度の誤差
altitudeAccuracy高度の誤差
heading方角(0.0~360.0)
speedスピード
timestampタイムスタンプ

エラー情報パラメータ
codeエラーコード
0 不明なエラー
1 位置情報の取得を拒否された
2 位置情報を取得できない
3 タイムアウト
messageエラーメッセージ

折角このようなAPIがあるので、blazorから呼び出してみたい。ただ、getCurrentPosition関数やwatchPosition関数のコールバック関数が必要になるので、そこから、C#コードを呼び出すようにする。

var GPSLib = {
	・・・
	GetPosition : function(helper,complete,errorhandler) {
	    navigator.geolocation.getCurrentPosition(
	        function(e) {
	            // パラメータを作成(eをそのまま渡してもキャストされないので・・・)
	            var prm = {
	                coords: {
	                    latitude: e.coords.latitude,
	                    longitude: e.coords.longitude,
	                    altitude: e.coords.altitude != null ? e.coords.altitude : 0,
	                    accuracy: e.coords.accuracy != null ? e.coords.accuracy : 0,
	                    altitudeAccuracy: e.coords.altitudeAccuracy != null ? e.coords.altitude : 0,
	                    heading: e.coords.heading != null ? e.coords.heading : 0,
	                    speed: e.coords.speed != null ? e.coords.speed : 0
	                },
	                timestamp: e.timestamp
	            };
	            // C#コードの呼出し
	            helper.invokeMethodAsync(complete,prm);
	        },
	        function(e) {
	            // パラメータを作成(eをそのまま渡してもキャストされないので・・・)
	            var prm = {
					code: e.code,
					message: e.message
				};
	            // C#コードの呼出し
				helper.invokeMethodAsync(errorhandler,prm);
	        }
	    )
	},
・・・
};
// 位置情報(一応、すべてのプロパティを得られるようにした)
public class GeoInfo {
    public Coords coords { get; set; }
    public long timestamp { get; set; }
}
public class Coords {
    public double latitude {get; set;}	// 緯度
    public double longitude {get; set;}	// 経度
    public double altitude { get; set; }	// 高度
    public double accuracy	{get; set;} // 緯度・経度の誤差
    public double altitudeAccuracy {get; set;}	// 高度の誤差
    public double heading {get; set; }	// 方角(0.0~360.0)
    public double speed  { get; set; } // スピード
}
// エラー情報
public class GeoError {
	public int code { get; set; }
	public string message { get; set; }
}

// 位置情報取得完了時処理
[JSInvokable]
public async Task OnGetPositionComplete(GeoInfo e) {
    Latitude = e.coords.latitude;
    Longitude = e.coords.longitude;
    Orient = e.coords.heading;
	・・・
    this.StateHasChanged();
}

// 位置情報取得エラー時処理
[JSInvokable]
public void OnGetPositionFail(GeoError e) {
	ErrorMessage = $"位置情報取得エラー:{e.code} - {e.message}";
	this.StateHasChanged();
}

・・・
	// GetPositionの呼出し
	await JSRuntime.InvokeVoidAsync(
	    "GPSLib.GetPosition",
	    DotNetObjectReference.Create(this),
	    "OnGetPositionComplete","OnGetPositionFail");
	);

GoogleやBing等のMAP APIと組み合わせると、スマホやタブレット用に結構面白いアプリが簡単に作成できる。(歩いた場所のトレースや現在位置からスポット検索等)

イベントの発生頻度を考えると、Server Side版よりもWASM版で作成した方が良いと思われる。(保持できるデータの上限が気になるところだが・・・)

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