.NET Reflection

.NETにしろJavaにしろ、リフレクションを使うのは、あまり実行時の効率が良くないのだろうが、呼出回数が多くなければ、それほど気にならない。

私の場合、どんなときに使うかというと、クラスを特定しない、コンバーターライブラリなどに使用している。

例えば、CSVと定義ファイルから、クラスインスタンスにCSVの項目を動的にマッピングさせるライブラリなど。

以下のような感じで使用している。

<!--
	マッピング定義
	Column -- CSVカラム
		Index : カラム位置
		MapProperty : 設定するプロパティ名
		FuncName : 変換メソッド
-->
<Mapping OutputColumns="5">
  <Column Index="0" MapProperty="EmpNo" FuncName="ConvertEmpNo"/>
  <Column Index="1" MapProperty="ExecDate" />
	・・・
</Mapping>
/// <summary>
/// クラスインスタンスへの値セット(Reflection版)
/// </summary>
/// <param name="instance">インスタンス</param>
/// <param name="prop">プロパティ情報</param>
/// <param name="value">値</param>
/// <typeparam name="U">インスタンスの型</typeparam>
public static void SetProp<U>(U instance, string prop, object value) {
    var p = typeof(U).GetProperty(prop);
    if (p == null) {
        throw new ArgumentException($"クラス「{typeof(U).ToString()}」にはプロパティ{prop}が存在しません");
    }
    p!.SetValue(instance,value);
}

ちなみに、FuncNameのメソッドも同様にReflectionを使用して呼び出す。

他にExpressionを使用する方法もあるのだが、式をコンパイルした物をどこかに保存しない場合はReflectionの方がまだ効率が良いかなと・・・

まぁ、データ数がそれほど多くなければ実行速度はあまり気にならない程度。作りやすさや作成時間と実行時間とのトレードオフといったところかな。

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

ASP.NET Core SignalR-厳密に型指定されたハブ

以前、SignalRのHUBについてチョット書いたことがあるけれど、見落としていたことがあったので、今更ながら情報を追加しておく(汗)

SignalRのHUBだが、クライアントへメッセージを渡すメソッドに関して、Interfaceを定義しておけば、nameofを使用して文字列ベタ打ちじゃなくても済むというようなことを書いたが、実はもっと、簡単な方法があった。(HUBに関してだけだけど・・・)

その方法とは、作成するHubのベースクラスをHubではなく、Hub<T>にすること。このTはインターフェイスを表す。例えば、以下のような感じ。

/// <summary>
/// Hub Methods(Called by Client)
/// </summary>
public interface IHubMethods {
    Task LogOn(string UserName);
    Task SendMessage(string UserName, string Message);
    Task LogOff();
}
/// <summary>
/// Client Methods(Called by Hub)
/// </summary>
public interface IClientMethods {
    Task ReceiveMessage(string UserName, string Message);
    Task LogOnMessage(string UserName);
    Task LogOffMessage(string UserName);
}
publc class MyHub : Hub<IClientMethods>, IHubMethods {
	・・・
	public async Task SendMessage(string UserName, string Message) {
		// 普通なら、
		// await Clients.All.SendAsync(nameof(IClientMethods.ReceieveMessage),UserName,Message);
		// と書くのが普通だが、下記で上記と同様となる。(I/Fのデフォルト実装でもしているのかな?
		// チョット気持ち悪いんだが・・・)
		await Clients.All.ReceieveMessage(UserName,Message);
	}
	・・・
}

I/Fのデフォルト実装でも勝手にしているのか、I/Fに定義されたシグネチャで呼び出すとSendAsyncが呼ばれるようだ。私的には裏で何かしらやられるのはあまり好きでは無いが、確かにこれなら、メソッド名とパラメータを間違えることはないね。

SignalRを使うために結構MSのサイト見ていたつもりなのだけど、見落としていました・・・

※ちなみに、この方法を使うと、Clients.XXX.SendAsyncは使用不可となるので注意。

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

GitLabのデフォルトブランチ

昨年あたりから、GitLabを使用しているのだが、最近、ローカルのプロジェクトをpushして、そこからcloneを作ろうと思ったら、

warning: remote HEAD refers to nonexistent ref, unable to checkout.

という、警告が出て、ソースがチェックアウトされない状況。

色々、調べた結果GitLabのデフォルトブランチ名がmasterからmainに変更されたためのようだ(変更は可能)。確かに、githubではmasterからmainへ変更するような事が書かれており、それに伴って、GitLabでもデフォルトブランチ名をmainに変更したのだろう。

つまり、上記の変更以降に作成されたプロジェクトをgit cloneで取得する場合、デフォルトで、mainブランチを持ってくるということになってしまっていて、ローカルで作成したmasterブランチはデフォルトではチェックアウトできない。

git clone -b masterとすれば、問題無くcloneできるのだが、あまり綺麗じゃ無いな。

やはり、ローカル側のgitのデフォルトブランチ名をmasterからmainに変更すべきなのだろう。git configコマンドで、–systemか–global(私の環境では、systemで設定されていた)

init.defaultbranch master

をmainに変更すればいいだけのようだし・・・

結構、いきなり動かなくなったので、色々焦ってしまったよ・・・

カテゴリー: 技術系, 開発環境 | コメントする

ASP.NET Core MVCでの入力値検証対象外設定

ASP.NET Core MVCでは、入力先となる、クラスに検証用の属性を付けることで、入力フォームの検証を簡単に行う事が可能である。

ただ、@modelで指定したModel全てが検証対象となるため、ここにI/F用のエラーメッセージなどのプロパティを入れると、何も指定しない限り、検証エラーとなってしまうようだ。

例)

public class AddModel {
    public Person Person { get; set; } = null!;
    public string Message { get; set; } = null!;
}

public class Person {
    [Key]
    [Required(ErrorMessage ="社員番号は必須です。")]
    public string EmpNo { get; set; } = null!;
    [Required(ErrorMessage ="氏名は必須です。")]
    public string Name { get; set; } = null!;
    [Required(ErrorMessage ="メールアドレスは必須です。")]
    [EmailAddress]
    public string Mail { get; set; } = null!;
    public DateTime Birthday { get; set; }
}
@model AddModel

<span>@Model.Message</span>

社員番号:<input for="Person.EmpNo"/> @Html.ValidationMessage("Person.EmpNo")<br/>
氏名:<input for="Person.Name"/> @Html.ValidationMessage("Person.EmpNo")<br/>
・・・
<button asp-action="AddRecord">追加</button>
・・・
public IActionResult AddRecord(AddModel model) {

    if (!ModelState.IsValid) {	// Messageのせいで常にfalseとなってしまう・・・
        model.Message = "不正な入力があります";
        return View("Add",model);
    }
	・・・
	var cnt = ctx.Personel.Where(v=>v.EmpNo == model.Person.EmpNo).Count();
	if (cnt != 0) {
        model.Message = $"社員番号'{model.Person.EmpNo}'は既に登録されています";
        return View("Add",model);
	}
	・・・
}

いろいろ、試してみたが、中々、解決できなかった。検証用の属性を調べた結果、ValidateNever属性を付けると、検証対象外となるようなので、試したらうまく動いたよ。

using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
・・・
public class AddModel {
    public Person Person { get; set; } = null!;
    [ValidateNever]
    public string Message { get; set; } = null!;
}
カテゴリー: .NET, asp.net core, C#, 技術系 | コメントする

dotnet watchを使用した動的なWeb開発

.NET6から実行中にソースコードを変更すると、即時に動作が変更される、所謂ホットリロード機能が本格的に取り入れられた。

ロジックなどの変更にも対応しているが、Webアプリの画面デザイン等の変更⇒確認の流れが非常に楽になるので、お薦め。

Visual Studio 2022などでは、VSのUIからホットリロードモードに変更することが可能だが、これは裏でdotnet watchコマンドが実施されている。

Visual Studio Codeでホットリロードを行いたい場合は、dotnet watchコマンドでイメージを起動すれば同じようにプロジェクト中のソースコードなどが変更された場合、ホットリロードで画面やロジックなどが即座に変更される。(ホットリロード対応できない場合は再起動するかどうか聞いてくる)

下記はwebappテンプレートで作成されたIndex.cshtmlだが、dotnet runの代わりにdotnet watchコマンドで起動し、ソース(Index.cshtml)を変更しただけで、即座に画面に反映される。CSSファイルの内容変更などでも、同様である。

編集前
Index.cshtmlを編集して保存

特に、フォームやロジックを構築後にデザインのみを色々と試したい時などに便利。

ちなみに、この状態から、<form>タグを追加して、POSTハンドラを.cshtml.csに追加しても、再起動必要なく、反映させることができた。

うまく使うことによって、開発効率が上がるのではないかと思う。

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

JSONPの動的呼出

ブラウザのJavascriptからWeb APIを呼び出したいのだが、APIの提供先が、CORSに対応していないので、仕方なくJSONPで呼び出すことにした。(結構大手が提供しているサービスなのに・・・)

で、JSONPで動的なパラメータを使用(例えばMAP上で位置をクリックした場所の座標情報をパラメータと)して呼び出したいので調べたら、domにscriptタグを動的に追加するのが一般的なやり方のようだ。

しかし、イベント発生時にその都度追加して行くとどういう動作するか不安なので、作成するscriptタグは一つにしたい。

で、以下のようなコードを書いてみたら、うまく動きましたぜ。

var url = "https://・・・";
url += "&p1=" + param1;
url += "&p2=" + param2;
url += "&callback=callbackfunc";

// JSONPで呼び出すので、本体にscriptエレメントを追加してやる。

var scr = document.getElementById("JSONPScript");

if (scr != null) {  // すでに存在すれば削除
    document.body.removeChild(scr);
}
// 新たに追加
scr = document.createElement("script");
scr.id = 'JSONPScript';
scr.src = url;
document.body.appendChild(scr);

まぁ、JSONPはちょっと危険なのであまり使いたくは無いけど、PROXY使うと、遅くなるし、大手が提供しているAPIだから、まぁいいかなと・・・

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

C#10 ファイルスコープnamespace

C#10では、1ファイルに1つのネームスペースを記述する場合、下記のように、namespaceを{}でくくる必要が無くなった。

// C#9以前
namespace XXXX.Pages {
	public class IndexModel : PageModel
	{
		・・・
	}
}
// C#10以降
namespace XXXX.Pages;

public class IndexModel : PageModel
{
	・・・
}

これは見た目にも分かりやすいし、プロジェクトテンプレートもこういう形でソースを生成してくれるのだが、dotnet-ef等、一部のツールで生成されるソースは未だC#9以前の形で生成されてしまう。早く統一してくれないかな・・・

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

.NET6 Blazor selectタグ multiple @bind

なんか、呪文みたいなタイトルになってしまったが、言いたいことは、Blazorが.NET6から、複数指定(multiple)selectのbindをサポートしたというお話。

どういうことかというと、以下の例を見て頂ければわかると思う。

@page "/"

<h2>Multiple Select Bind</h2>

<select style="width:200px" multiple size="6" @bind="SelectedColors">
	<option value="red">赤</option>
	<option value="blue">青</option>
	<option value="green">緑</option>
	<option value="magenta">紫</option>
	<option value="yellow">黄</option>
	<option value="orange">橙</option>
</select>
<br/>
@foreach(var c in SelectedColors) {
	<div style="width:100px;height:30px;background-color:@c"></div>
}

@code {
	protected string[] SelectedColors = new string[] {};
}
実行結果

なお、配列のサイズは動的に変更される。つまり、上記の例では、SelectedColors.Length=3だが、下記の実行例では、SelectedColors.Length=2となる。

実行例2

検索とかで、複数カテゴリなどを選択するときなどに結構便利。

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

.NETプロジェクトでDLLの参照

.NETプロジェクトで自社製DLL等を直接プロジェクトから参照したい場合などがある。

dotnet cliで出来ないかな~と思って調べたけど、nugetパッケージやproject参照は可能だが、DLLの直接参照は出来ないらしい。

いろいろ調べたが、以下のような感じで、プロジェクトファイル(C#なら.csproj)に手動で追加するしかないようだ。

<Project Sdk="Microsoft.NET.Sdk">

	・・・

    <ItemGroup>
	    <Reference Include="jp.co.hoge.mydll"><!-- ネームスペース -->
	        <SpecificVersion>False</SpecificVersion><!-- フレームワークバージョン特化? -->
	        <HintPath>/usr/share/sharelib/mydll.dll</HintPath><!-- アセンブリの在処 -->
	    </Reference>
	</ItemGroup>

</Project>

面倒だから、dotnet cliから参照追加できるようにして欲しいな・・・

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

global using

C# 10から、global usingが使えるようになり、どのファイルでも使用するようなネームスペースは各々のファイルでusingを記述することなく、.csprojで定義することが可能となった。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings><!-- 暗黙的なusingを使用(デフォルト) -->
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <Using Include="System.Net.Sockets" /><!-- global usingの追加 -->
  </ItemGroup>

</Project>

ImplicitUsingsがenableの場合、暗黙的に下記のようなglobal usingが定義される。

// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
// <auto-generated/>
global using global::Microsoft.AspNetCore.Builder;
global using global::Microsoft.AspNetCore.Hosting;
global using global::Microsoft.AspNetCore.Http;
global using global::Microsoft.AspNetCore.Routing;
global using global::Microsoft.Extensions.Configuration;
global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Extensions.Hosting;
global using global::Microsoft.Extensions.Logging;
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Net.Http.Json;
global using global::System.Threading;
global using global::System.Threading.Tasks;

使い方によっては、それなりに便利だと思うけど、見えないところで勝手にジェネレートする設定をデフォルトにするのはやめて欲しいな・・・

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