asp.net core Web API CORS

asp.net core Web APIではデフォルトでCORS (Cross Origin Resource Sharing) がディセーブルになっているため、別サイトからアクセスすると、クライアント側でエラーが発生してしまう。

CORSを有効にするには、Startup.csに以下のような、設定が必要となる。

public IConfiguration Configuration { get; }
public Startup(IServiceConfiguration configuration) {
  Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
  // CORSポリシーの追加
  services.AddCors(options =>
  {
    options.AddPolicy("AllowAllOriginsPolicy", // 名前は何でも良い
      builder =>
      {
        // この例では全ホストから受付け可能(どうせ、FWでブロックされるし・・・)
        // ホストを制限するには、.WithOriginsメソッドを使用
        // ヘッダを制限するには、.WithHeadersメソッドを使用
        // HTTPメソッドを制限するには、.WithMethodsメソッドを使用
        builder.AllowAnyOrigin();
       });
  });
  services.AddControllers();
}

↑の例では、全てのホストからの接続を許可している。
特定のホストだけ許可するには、.WithOriginsを使用する。

    builder.WithOrigins("http://boo.foo.com","http://www.boo.foo.com");

後は、Configuraメソッドの中で、全てのエンドポイントに適用するか、エンドポイント毎に適用するかによって、指定が異なる。
全てのエンドポイントに適用するには、Configureメソッドの中で、

  app.UseCors();

を呼び出す。

詳しくは、

https://docs.microsoft.com/ja-jp/aspnet/core/security/cors?view=aspnetcore-3.1

を参照。

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

SignalRについて

この間SignalRで云々というエントリを投稿したが、SignalRそのものの説明が無かったので、簡単に説明しておこうかと。

SignalRはasp.net coreアプリとして、HUB(サーバ)を作成することにより、複数のクライアントとHUB間でメッセージのやりとりを行う事ができる仕組み。

クライアント用ライブラリは.net,java,javascriptが用意されている。

クライアントから、HUBに接続し、下記のような事を行う事が可能。

  • サーバからのPush配信
  • クライアントからHUB経由で全クライアントまたはグループ内クライアントへメッセージ一斉送信
  • クライアントからHUB経由で特定のクライアントへメッセージ送信

実装想定例としては、

  • サーバからの高頻度の更新を必要とするアプリ、例えばGPSを利用した位置特定および周辺情報のリアルタイム表示
  • システム監視アプリ
  • チャットなどのコミュニケーションアプリ
  • ホワイトボードなどのコラボレーションアプリ

仕組みとしては、クライアント~HUB間の接続を行った後、RPCを用いて、クライアント側からHUBのプロシジャ(実装はメソッド)を実行したり、クライアント側でHUBからのメッセージ受信イベントハンドラ(メッセージ毎に定義可能)を定義して、特定処理を行う。

簡単な例を挙げると、以下のような感じでチャットアプリのようなものを作成することが可能。

HUB側
// HUBに接続されている、全てのクライアントにメッセージ送信
public async Task SendMessageAll(string user, string message) {
              await Clients.All.SendAsync("ClientMessge",user,message);
}
// HUB側からのPush通知
private async Task Notify(string message) {
              await Clients.All.SendAsync("SystemNotify", message);
}
Client側
HubConnection con = HubConnectionBuilder()
            .WithUrl("http://・・・")
            .Build();

// メッセージハンドラの登録
con.On<string,string>("ClientMessage",
      (user,message) => {
                    // 処理
      }
);
con.On<string>(""SystemNotify",
      (message) => {
                    // 処理
      }
);
・・・
// 現在HUBに接続している全てのクライアントにメッセージ送信要求
await con.InvokeAsync("SendMessageAll",MyUserName,Message);

HUB側ではClients.Allの他

  • Clients.Other                    要求元以外のクライアント
  • Clients.Group                   特定のグループ
  • Clients.User                     特定のユーザ
  • Clients.AllExcept             指定されたユーザ以外全て
  • Clients.Caller 呼出元のクライアント

等、送信先の指定が可能。

クライアント側は

  On<パラメータ型,[パラメータ型,…]>(メッセージID,メッセージハンドラ)

の形で複数種類のメッセージをハンドリングする事が可能。
※パラメータの型はHUB上の定義と合っていなければハンドリングされない。

blazor wasm版などと組み合わせると、結構面白いアプリを作成することができるのでは無いだろうか。

ちなみに、クライアント,HUB双方でメソッド名やメッセージ名を文字列で扱っているが、バグの温床となり得るので、Interfaceを作成して、nameof(メソッド名)やnameof(メッセージ名)等とすると良いだろう。

詳細は下記などを参照
https://docs.microsoft.com/ja-jp/aspnet/core/signalr/introduction?view=aspnetcore-3.1

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

asp.net core blazor(server side)で、SignalR

asp.net core blazor(server side)でSignalRのテストをしてみた。同じプロジェクト中にHubを作成して簡易チャットを作成。

接続や処理要求は良いのだが、問題が1つ。それは、ページを閉じたり、ページから移動した時にHub側で切断イベント(OnDisconnectedAsync)が発生しないこと。色々調べたんだけど、結局、クライアント側のアプリから明示的に切断しないと発生しないみたい。

Keepaliveとかのタイマー値をいじってみたが、つながっているのに切断イベントが上がったり・・・
多分、クライアントとHub双方で値を調整しなければいけないんだろうね。

じゃぁ、「ページ閉じた時に明示的に切断したれと」思い、blazorページにIDisposableインターフェースをImplementしてDisposableメソッドを定義、その中で明示的に切断するようにしたら、Hub側でイベントが発生するようになりました。

@implements System.IDisposable
・・・
    //
    //  ページ破棄時に明示的にSignalRを切断してみる
    //
    void IDisposable.Dispose() {
        // conはHubConnectionのインスタンス
        // 明示的に接続を破棄
        con.DisposeAsync();
    }
}

まぁ、これが正しい方法かどうかは分からないけど・・・

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

blazor Server Sideアプリのサブサイトへの公開

asp.net blazor server sideアプリを/blazorAppのようにサブサイトに公開するには、Pages/_Host.cshtml中の<base>タグを<base href=”/blazorApp/”/>のように書き換えるだけではNGで、Startup.cs中のConfigureメソッドに下記を追加しなければならない。

app.UsePathBase("/blazorApp")

※備忘録として

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

blazor JSからC#メソッド呼び出し

asp.net core blazorでJavascriptからC#メソッドを呼び出す方法をMSのサイトや、他のサイトで検索した結果、staticメソッドや他クラスインスタンスメソッドを呼び出す方法については書いてあったが、ページクラスのメソッド呼出しについて書いてあるサイトが見つからなかった。

ページクラスのメソッドが呼べれば、クラス変数がそのまま使用できるので、変数とバインドされているHTMLエレメント値を直接いじれるので便利だと思い、色々試してみた。

サイトに書かれていた説明では、呼出されるC#メソッドには[JSInvokable]属性をつける必要がある。これはどのサイトでも同じことが書かれている。

staticメソッドの場合は、 下記のような感じでJavascriptから呼び出すことが可能。

Dotnet.invokeMethod(<アプリ名>,<メソッド名>[,パラメータ1[,パラメータ2...]])
または
Dotnet.invokeMethodAsync(上記と同様)

ただし、staticメソッドなので、バインドされたページインスタンス変数にはアクセスできない。

もう一つ例が載っているのが、ヘルパークラスを介した呼出し。

C#側
var helper = new MyJsHelper();
await JSRuntime.InvokeVoidAsync("JsFunc",
  DotNetObjectReference.Create(helper));

JS側
function JsFunc(helper) {
  helper.invokeMethodAsync(・・・); // invokeMethodも使用可
}

でも、同じようにバインドされたページインスタンス変数にはアクセスできないよね。(もちろん、ページインスタンスをヘルパーインスタンス内に保存すれば大丈夫かもしれないけれど・・・)

で、上記を見てもしかしてと思って以下のようなコードを書いてみた。

C#側
private string Mesg = "";
・・・
[JSInvokable]
public void CSMethod(int p1, double p2, string p3) {
  Mesg = $"p1={p1}, p2={p2}, p3={p3}";
  this.StateHasChanged(); // JSから呼び出された場合は自動更新されないので
}
・・・
  await JSRuntime.InvokeVoidAsync("JsFunc",
    DotNetObjectReference.Create(this), 
    "CSMethod",10,3.14,"Hello");

JS側
function JsFunc(helper,func,p1,p2,p3) {
  helper.invokeMethodAsync(func,p1,p2,p3);
}

見事に動いてくれました。

で、これ使って何をやりたかったかというと、BingMap等のイベントハンドラをC#で書きたかっただけなのです。実際には、ピン等を追加したりするためにJS関数を細切れに作って、C#から呼び出すことになるんだけどね・・・

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

blazor wasm版の公開

blazor wasm版を公開してみた。
こちらは、blazor Server Side版と異なり、dotnet publishで作成されたフォルダをそのまま、Webサイトにコピーするだけだ。

ただし、Web Assembly(.wasm)のMimeタイプをWebサーバに登録する必要がある。

とりあえず、コピーするフォルダだが、以下となる。

<アプリケーションルート>/bin/Release/<対象フレームワーク>/publish/<アプリケーション名>/dist

このフォルダーの中身を全て、HTTPサーバの公開フォルダにコピーする。

なお、公開する際に気を付けなければいけないのは、wwwroot/index.html中の<basse>タグの扱いだ。デフォルトでは、<base href=”/”>となっていて、HTTPサーバのドキュメントルートを使用する設定になっている。コンテンツの配置先が、サブフォルダの場合、このままでは、ルーティングできないため、エラーとなってしまう。その場合は、<base>タグの値をサブフォルダ込みに変更しよう。

また、HTTPDの設定だが、Apache HTTPDの場合、以下のような設定追加が必要。

    # Mime Typeの追加
    AddType application/wasm .wasm
    AddType application/octet-stream .dll
    # 圧縮の設定(必須ではないが、MSのサイトには例として載っている)
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/css
        AddOutputFilterByType DEFLATE application/javascript
        AddOutputFilterByType DEFLATE text/html
        AddOutputFilterByType DEFLATE application/octet-stream
        AddOutputFilterByType DEFLATE application/wasm
        <IfModule mod_setenvif.c>
            BrowserMatch ^Mozilla/4 gzip-only-text/html
            BrowserMatch ^Mozilla/4.0[678] no-gzip
            BrowserMatch bMSIE !no-gzip !gzip-only-text/html
        </IfModule>
    </IfModule>

以上で、blazor wasm版をHTTPD経由でアクセスする事が可能となる。
ちなみに、今回公開したのは、下記URL。
http://blazor.ohwaki.jp/canvas

以上、blazor Server Side版より簡単に公開可能。

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

VSCode Remote-SSHでリモートファイル編集

最近、Linux VPS上に(趣味で)でC#コードやHTMLを書く事が多い。

最初はvim等を使っていたが、もう少しいい方法がないものかと探していたら、Windowsクライアントで使用している、VSCodeからSSH経由でリモートファイルの編集が可能な、Remote-SSHという拡張機能を見つけた。

前提条件として、リモート側にVSCodeがインストールされていることと、クライアントからSSH接続が可能なこと。

まず、ローカル側のVSCodeにRemote-SSH拡張機能をインストール。

後は、メニューの[表示]-[コマンドパレット](Ctrl-Shift-p)で、コマンドパレットを開き、Connect to Host…を選ぶと、接続先を聞いてくるので、<リモートのユーザ名>@<ホスト名あるいはIP>と言う形で入力すると、新しいVSCodeウィンドウが開き、リモートユーザのパスワードを尋ねてくるので、パスワードを入力すればリモートとの接続が完了し、リモートファイルやリモートディレクトリの編集が可能となる。

なお、接続する、「リモートユーザ@ホスト名」は保存することが可能。

リモートのVSCodeに拡張機能を入れておけばコンパイルやデバッグも可能となる。

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

blazor server sideアプリをCentOS7+Apacheで公開してみた

以前にも書いたとおり、dotnet core 3からblazor server sideアプリが正式サポートされるようになった。残念ながら、wasm版は3.1からのサポートとなる。(βでは動作確認済み)

で、blazorというかaps.net coreのアプリを実環境に公開してみたくなったので、色々調べてみた。

まず、このブログが動いている、VPSはCentOS6なので、dotnet core 3は動作しない。仕方ないので、CentOS7のVPSを別途契約。

そのVPS上にdotnet core 3 sdkを導入後、簡単なblazor server sideアプリを作成。
dotnet runで開発モードにて動作確認後、下記コマンドでリリース版を作成。

dotnet publish --configuration Release

これで、bin/Release/<target framework>/publishフォルダにアプリケーションがビルドされる。ビルドされたフォルダ中に、<プロジェクト名>.dllが作成されるので、下記コマンドで動作する事を確認する。

dotnet <プロジェクト名>.dll

何も設定せずに動かしてみると、http://localhost:5000およびhttps://localhost:5001でListenしている。待受けアドレスやポートを変更するには、同一フォルダ中のappsetting.jsonに下記セクションを加えるのが簡単な方法だ。

  "Kestrel" : {
    "EndPoints" : {
        "Http" : {
          "Url" : "http://・・・:<待受けポート番号>"
        }
    }
  },

これを外部に公開するのだが、今回はapache httpdのvirtual host機能と、proxyおよびURL書換機能を利用して公開することにした。(一番簡単だと思う)

Kestrelの待受けURLをhttp://localhost:5000とした場合、以下のように設定したconfファイルを/etc/httpd/conf.dに置いてhttpdをリスタートしよう。

# URLで宛先切替
# 一般ホームページ用
<VirtualHost *:80>
        DocumentRoot /var/www/html
        ServerName www.hoge.jp
</VirtualHost>
# asp.net coreアプリ用
# URLの書換はblazor server sideアプリがSignalRを使用するために必要
# 最初、これが無かったので、ページは表示されるけど、イベントが
# ハンドリングされなかった・・・
<VirtualHost *:80>
        ServerName aspcore.hoge.jp
        ProxyPass / http://localhost:5000/
        ProxyPassReverse / http://localhost:5000/
        RewriteEngine on
        RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
        RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
        RewriteRule /(.*) ws://127.0.0.1:5000/$1 [P]
</VirtualHost>

とりあえず、この状態で、dotnet <プロジェクト名>.dllでアプリを実行、外部からアクセスしてみると、ちゃんと動作。
設定ファイルのコメントにも書いてあるけど、最初ProxyPassとProsyReversePassだけ設定して動かしたら、ページ自体は表示されたけど、イベント処理がNG。書換ルール追加を忘れないように(汗)

とりあえず、疎通はうまくいったので、アプリをサービス化しよう。
方法は https://docs.microsoft.com/ja-jp/aspnet/core/host-and-deploy/linux-apache?view=aspnetcore-3.0 などに書かれているように、 /etc/systemd/system にサービス定義ファイルを作成して、サービスを開始してあげればOK。

[Unit]
Description=サービスの説明

[Service]
WorkingDirectory=<作業ディレクトリ>
ExecStart=/usr/bin/dotnet <dllのフルパス>
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=<Syslogに書き出すときのprefix>
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production 

[Install]
WantedBy=multi-user.target

ちなみに、上記リンクのdotnetパスは/usr/local/binとなっているが、普通にdotnet core sdkをインストールすると、/usr/binにdotnetコマンドが置かれるようだ。
サービス定義後、下記コマンドでサービスを有効化して、起動、状態確認を行う。

# systemctl enable <サービスファイル名>
# systemctl start <サービスファイル名>
# systemctl status <サービスファイル名>
● <サービスファイル名> - <サービス説明>
   Loaded: loaded (/etc/systemd/system/<サービスファイル名>; enabled; vendor preset: disabled)
   Active: active (running) since ・・・ ago
 Main PID: 9999 (dotnet)
   CGroup: /system.slice/<サービスファイル名>
           mq7906 /usr/bin/dotnet <DLLフルパス>

これで、一応公開完了。
実際には結構色々なところで躓いているので、備忘録的に投稿してみた。

ちなみに、公開したページは以下のリンクから参照可能。
http://dnetapp.ohwaki.jp/gradation

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

asp.net core 3.1 preview 2 – blazor wasm app

dotnet core 3.1 previewでWeb Assemblyのみで動作する、Blazor WebAssembly Appが開発できるようになった。

dotnet core 3.0まではBlazor Server Appつまり、サーバーサイドで、.netの処理を行い、クライアント(ブラウザ)と通信を行って、結果を表示する方式のものしか開発できなかったのだが、Blazor WebAssembly Appでは、サーバーにアクセスするのは、ページロード時のみで、ページが読み込まれた後は全てクライアント側のWebAssembly中で処理が行われる。

Server AppとWebAssembly Appのソースは基本的に互換性があるようだ。(ただし、.netライブラリによってはサポートされないものもある)

試しに、Server Appのrazorページをコピーしてみたら、キチンと動作した。

Server Appと比べてbuildに時間がかかるのと、生成されるWebAssemblyのサイズが大きいためか、最初のページローディングには少々時間がかかる点が気になるが、一度ローディングしてしまえば、クライアント側で全て処理するので、処理している間にWebサーバーが落ちてしまっても問題無い。(実際にロード後にサーバーを落としてみたが、問題無く動作した。)

結構面白い技術だね。

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

ASP.NET Core,Blazorのレイアウト

以前、Blazorの記事を書いたが、dotnet core 3.0が正式にリリースされたので、Blazorに関しても色々といじくり回している。

Blazor等ASP.NET Coreのアプリをテンプレートから作成すると、Navigationメニュー付きの画面になってしまうが、これを変更するには、デフォルトのテンプレートを編集するか、別のテンプレートを作成して、ページ内でレイアウトテンプレートを指定する。

テンプレートはASP.NET Coreの場合とBlazorの場合ではレイアウトファイル名やフォルダが異なる。

ASP.NET Coreアプリの場合
・場所
<プロジェクトルート>\Pages\Shared
・ファイル名
_Layout.cshtml

Blazorの場合
・場所
<プロジェクトルート>\Shared
・ファイル名
MainLayout.razor

これらのファイルを直接変更すると、ページソースを変更しなくてもレイアウトを変更できる。

ちなみに、ページ毎に別のレイアウトを作成したい場合は、上記のフォルダにレイアウトファイルを作成して、ページ記述(.cshtmlや.razor)中に@layoutで指定する事が可能。
例えば、ASP.NET Coreアプリで\Pages\Shared\MyLayout.cshtmlを作成して、それをページに適用したい場合は、ページ中に@Layout=”MyLayout”とすれば良い。

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