20190909のC#に関する記事は6件です。

ASP.NET Core でSPAではなくVue.jsを利用してみる(4)

クライアント側にVue.jsをSPA(シングルページアプリケーション)ではない方法で利用することについて記述していきます。

全体
 ・Vue.jsとblumaの設定
 ・cssをblumaに変更
 ・バリデーションの変更
 ・Vue.jsでのコンポーネントの利用(本稿)

-Vue.jsでのコンポーネントの利用-

環境

「ASp.Net Core でPostgreSQLを利用してIdentityで認証を使えるようにする」で作成した環境にVue.jsを組み込み、cssフレームワークをblumaに変更します。
- VisualStudio2019 Ver.16.2.3
- ASP.NET Core 2.2
- PostgreSQL 9.6 インストール済み、接続用のアカウント作成済み
- EntityFramework
- Vue.js 2.6.10
- bluma 0.7.5

Vue.jsを使う理由

 すいません、タイトルに偽りありでした。ようやくVue.jsの記述にたどり着きました。本当はRazorPageでクライアントコンポーネントにVue.jsが使いたかっただけなのですが。
 事の発端はJQuery使いにくい、クライアント動作の為のHTMLはサーバーサイドで作っているのでソースが分かれてしまうのがいや、RazorPageの利点を生かしたままクライアント側のコンポーネントを再利用可能な状態で整理して作りたいという思いから、どうせやるなら.Net Core、それならLinuxで動かそう、それならDBもMySQLかPostgresだよねー、JQueryやめるならBootstrapも変えちゃえと思ってたらこんなことに...。

作るもの

 そんなに大きなものは作れないので、ボタンを押すとページをロックしてページが変わるか一定時間ロック状態にするボタンを作ります。

下ごしらえ

 とりあえず、画面ロックのJavaScriptとして、「wwwroot/js/utilities.js」を作り「_Layout.cshtml」のどこかで作ったスクリプトを読み込ませておきます。

utilities.js
// スクリーンロック
function lockScreen(message, timeout) {

    // ロック用のdivを生成
    var lockElement = document.createElement('div');
    lockElement.id = "screenLock";
    var textElement = document.createElement('div');
    textElement.innerText = message;
    lockElement.appendChild(textElement);

    // ロック用のスタイル
    lockElement.style.display = 'table';
    lockElement.style.height = '100%';
    lockElement.style.left = '0px';
    lockElement.style.position = 'fixed';
    lockElement.style.top = '0px';
    lockElement.style.width = '100%';
    lockElement.style.zIndex = '9999';
    lockElement.style.opacity = '0.9';
    lockElement.style.background = "white";

    textElement.style.display = 'table-cell';
    textElement.style.textAlign = 'center';
    textElement.style.verticalAlign = 'middle';
    textElement.style.margin = 'auto';
    textElement.style.fontSize = 'x-large';

    // ロックの追加
    var objBody = document.getElementsByTagName("body").item(0);
    objBody.appendChild(lockElement);

    if (timeout) {
        // タイムアウトが設定されていれば、そのmsec後にロック解除
        setTimeout(function () {
            // ロック画面の削除
            unlockScreen();
        }, timeout);
    }
    else {
        // タイムアウトが設定されていなければ、30秒後に解除
        setTimeout(function () {
            unlockScreen();
        }, 30000);
    }
}

// スクリーンロック解除
function unlockScreen() {
    var lockElement = document.getElementById('screenLock');
    var dom_obj_parent = lockElement.parentNode;
    dom_obj_parent.removeChild(lockElement);
}

 見ての通り、画面全体を覆う「div」を作っているだけです。画面が切り替わる場合は解除不要ですがページを更新しない場合は「unlockScreen」を呼び出してロックを解除した方がいいです。(タイムアウトするはずですが、それはちっと...)

コンポーネントの作成

 「wwwroot/vue/compornents」フォルダを作成し、その下に「button-with-lock.js」を以下のように作成します。

button-with-lock.js
Vue.component('button-with-lock', {
    props: {
        label: String,
        lockmessage: String
    },
    template: '<button type="submit" class="button is-primary is-outlined" v-on:click="lockOnClick">{{label}}</button>',
    methods: {
        lockOnClick: function () {
            lockScreen(this.lockmessage);
        }
    }
});

 最初の「button-with-lock」はコンポーネント名で、使う場合にはタグ名となります。
 「props」はタグ内の属性として外部から設定できるものです。
 「template」は、挿入されるHTMLにvue独自の味付けをしたものです。ここでは「v-on:click」でonclickイベントで後述の「methods」の「lockOnClick」を実行させます。また、propで設定した「label」に入っている文字列をボタンのテキストとなるように「{{label}}」(二つの中かっこでくくる)と、その内容に置き換わります。
 最後の「methosds」はスクリプトを記述しています。ここでは下ごしらえで作成した「lockScreen」に引数として「prop」に設定されている「lockmessage」を渡しています。

 なお、ES6のみを対象とする場合、「template」ではテンプレート文字列(「`」でくくり、改行も利用できる文字列)のほうが書きやすいです。IE11もサポートしたいので文字列にしていますので、改行を使いたい場合は行末に「\」を入れる必要があります。

コンポーネントンの利用

 作成したコンポーネントをログイン画面で利用してみます。
 「Areas/identity/Pages/Account/Login.schtml」の記述を以下のようにします。

Login.schtml
@page
@model LoginModel

@{
    ViewData["Title"] = "Log in";
}
<script src="~/vue/compornents/button-with-lock.js" asp-append-version="true"></script>

@*vueのターゲットになるようにidを設定する*@
<div class="section" id="vueTarget">

    (フォームの前半は省略...)

                <div class="field has-text-centered">
                    @*ボタンをコンポーネントに置き換える*@
                    @*<button type="submit" class="button is-primary is-outlined">ログイン</button>*@
                    <button-with-lock label="ログイン" lockmessage="ログイン中..."></button-with-lock>
                </div>
            </div>
        </div>
    </form>
</div>

<script>
    //vueを利用する
    var vm = new Vue({
    el: '#vueTarget',
    });
</script>

 最初に作ったコンポーネントを読み込んでいます。(多分後のvueインスタンス作成)直前でも大丈夫)
 全体の「div」の「id」を「vueTarget」としました。これはvueを使う目印になります。(名前は何でもいいです)
 最後のほうのログインボタンを作ったコンポーネントで置き換えています。このように「props」に設定している値を属性として指定できます。
 最後にvueインスタンスを作成すると、コンポーネントが実装されます。この時「el」で対象となる「div」の「id」を「vueTarget」指定したことで、この「div」内が対象となります。(つまり作ったコンポーネント「button-with-lock」が設定した「div」の範囲外なら何もしないということです。)
 これでデバッグを実行すると、ボタンをクリックするとロックされます。(見やすいように、サーバーの「login」の実装に数秒の待ち時間を入れたほうがわかりやすいです)

RazorPageのモデルと関連付ける

 上記のボタンはそれなりに使えると思います。
 ただ、実際には作ったコンポーネントにRazorPageのモデルから値を連携させたい場面があるので、ここではとりあえずロックメッセージをモデルから取得するように変更します。(実際にはすることはないと思う)

 まずはビューモデルである「Areas/identity/Pages/Account/Login.cshtml.cs」にロックメッセージのプロパティを追加します。

Login.schtml.cs
前の方は省略...

        [BindProperty]
        public InputModel Input { get; set; }

        public IList<AuthenticationScheme> ExternalLogins { get; set; }

        public string ReturnUrl { get; set; }

        // ログイン中のメッセージを追加
        public string TestLockMessage { get; set; } = "ろぐいんちゅうでっせー";

この後ろも省略

 ログイン中のメッセージとして「TestLockMessage」を追加しています。

 次にモデルをvueインスタンスの作成時に「data」で与えるのですが、この時ひと工夫が必要でした。(結構調べるのに手間取った)。
 ビューモデルを@Json.Serialize(Model)でシリアライズして渡そうとしたのですが、単純なビューモデルではなく「PageModel」を継承しているためにこのクラスのメンバが大きいうえに循環参照していたりしてエラーになってしまいます。循環参照は無視させると、時間がかかりすぎて帰ってきませんでした。何をしようとしているのやら...。
 調べていくと「Json.Serialize」の第2引数で与える「JsonSerializerSettings」の「ContractResolver」のメソッド「GetSerializableMembers」をオーバーライドしてシリアライズする対象を選別すればいいことがわかりました。「Utility」フォルダを作って「JsonConverterExceptPageModel.cs」を以下のように作成しました。

JsonConverterExceptPageModel.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebApplication1.Utility
{
    public static class JsonConverterExceptPageModel
    {
        static DefaultContractResolver resolver = new DefaultContractResolver();

        static public Microsoft.AspNetCore.Html.IHtmlContent Serialize(IJsonHelper jsonHelper, object value)
        {
            JsonSerializerSettings settings = new JsonSerializerSettings();
            NotInhelitContractResolver resolver = new NotInhelitContractResolver();
            settings.ContractResolver = resolver;

            return  jsonHelper.Serialize(value, settings);
        }

    }

    public class NotInhelitContractResolver : DefaultContractResolver
    {
        protected override List<MemberInfo> GetSerializableMembers(Type objectType)
        {
            Type pageModel = typeof(PageModel);

            List<MemberInfo> list = new List<MemberInfo>();
            foreach (var member in base.GetSerializableMembers(objectType))
            {
                if (member.DeclaringType != pageModel) list.Add(member);
            }

            return list;
        }
    }
}

 「GetSerializableMembers」のところで「PageModel」のメンバを除外するようにしています。使う環境によってはもう少し工夫がいるかもしれません。

 で、「Areas/identity/Pages/Account/Login.schtml」」を以下のように変更します。

Login.schtml
@page
@model LoginModel
@using WebApplication1.Utility   @*これを追加した*@

中略...

                <div class="field has-text-centered">
                    @*ボタンをコンポーネントに置き換える*@
                    @*<button type="submit" class="button is-primary is-outlined">ログイン</button>*@
                    <button-with-lock label="ログイン" v-bind:lockmessage="items.TestLockMessage"></button-with-lock>
                </div>
            </div>
        </div>
    </form>
</div>

<script>
    //vuew利用する
    var vm = new Vue({
    el: '#vueTarget',
    data: {  @*これを追加した*@
        items: @JsonConverterExceptPageModel.Serialize(Json, Model)
    }
    });
</script>

 作成したシリアライズ用のクラスを利用する為に、最初に「@using WebApplication1.Utility」を追加。
 ログインボタン「lockmessage」プロパティーは「vue」の「data」の「item.TestLockMessage」を参照させています。参照させる場合はこのように、「v-bind:prop名」の形になります。

 これで実行すればロックメッセージが関西弁に変わります。

 これで当初の目的であったRazorPageの利点も生かしながらクライアントサイドのコンポーネントをvue.jsで記述することができるようになったので、ひとまずこのシリーズは終わりかな。
 まあ、これを使って使えそうなコンポーネントができればどこかで公開するかもしません。

 C#.Net coreもvueも本格的には使っていないので、つたない部分があるかと思います。
 もっとすっきり記述する方法もあるかと思いますので、こうした方がいいのではとかありましたらご指摘ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】自作VIVEコンテンツでスタート位置を気にしなくて済む実装

VIVEのリセンター機能

Standing ModeSeated ModeにはSteamVRが用意している機能で
リセンター(再トラッキングしてポジションを中央に戻す)が可能ですが、Room-Scaleでのプレイには対応していません。

なので、VIVEを担いで営業デモに行った際にはスタート位置をバミるという手法を使っていました。

バミらないと、いきなりポリゴンにめり込んだ状態から始まったり、
明後日の方向を向いてしまって、後ろからしゃべりかけられてストーリーが展開する...なんてことが起きます。

位置と向きを補正

しかし、バミリは複数のコンテンツを体験していただく際には厄介です。(あれがこっち、これはあっちと余計なことに気を使います)
さらに、コンテンツの内容、作り方によってはスタート位置がまちまちになってしまうので、
部屋の大きさによっては、
・コンテンツAでは前方のスペースに十分なエリアが必要
・コンテンツBでは後方のスペースに十分なエリアが必要
といった具合に、それぞれのコンテンツで必要なエリアの幅が異なることで正しく体験できないという現象が起きかねません。

また、私は製作者側なのでデモ時に上記のような状況でも何をどう対処すればいいか瞬時に判断できますが、
コンテンツを使うユーザーからしたら意味不明で、ストレスでしかありません。

なので、コンテンツにリセンター機能を実装して組み込むことで、開始位置の固定を取り払いました。

コード

やっていることはシンプルで、
スタートと同時にコルーチン内でCameraRigの角度を回転させてCamera(プレイヤー)の向きを補正し、
その後にCameraRigの位置にCamera(プレイヤー)のトラッキング位置を考慮した補正をかけています。

using UnityEngine;
using System.Collections;
using UnityEngine.Events;
/// <summary>
/// Recenter your camera when you start VR. Attach to CameraRig.
/// </summary>
public class ReCenterCamera : MonoBehaviour
{
    [SerializeField, Tooltip("Set this child camera")]
    GameObject eyeCamera;

    [SerializeField,Tooltip("This event is skiped one frame")]
    UnityEvent startEvent;

    void Start()
    {
        StartCoroutine(ReCenterCoroutine());
    }

    IEnumerator ReCenterCoroutine()
    {
        Vector3 cameraRig_Angles = this.gameObject.transform.eulerAngles;
        Vector3 eyeCamera_Angles = eyeCamera.transform.eulerAngles;

        this.gameObject.transform.eulerAngles += new Vector3(0, cameraRig_Angles.y  - eyeCamera_Angles.y, 0);

        Vector3 cameraRig_StartPos = this.gameObject.transform.position;
        Vector3 eyeCamera_Pos = eyeCamera.transform.position;

        this.gameObject.transform.position += new Vector3(cameraRig_StartPos.x - eyeCamera_Pos.x, 0, cameraRig_StartPos.z - eyeCamera_Pos.z);

        yield return null;
        startEvent.Invoke();
    }
}

位置の補正に関しては前回の記事の内容をそのまま転用しています。

今回苦労したのは角度に関してです。

角度の調整

図解します。

黒い四角がカメラ(プレーヤー)、青い四角がCameraRigだとします。
それぞれのY軸回りのローテーションはy₁ , y₂です。
カメラ(プレーヤー)をVR起動時に、必ずA地点の方に向けた状態でスタートしたいという想定です。

シンプルな計算で、実装できます。
カメラ(プレーヤー)とCameraRigのY軸回りのローテーションの差を計算して、CameraRigのY軸回りのローテーションに足すだけです。

今回のプログラムでは加算代入によってy₂+y₂-y₁の部分を実装しています。

this.gameObject.transform.eulerAngles += new Vector3(0, cameraRig_Angles.y  - eyeCamera_Angles.y, 0);

なぜコルーチンなのか

なぜ、コルーチンにしているのかにも、きちんとした理由があり、今回苦労した原因です。

その原因とはトラッキングが完了してから補正する必要があるということです。
自分の今いる座標、回転座標が定まっていない(トラッキングが完了していない)状態で補正をかけても
意味がありませんよね。

なので、
①Updateでトラッキングの処理が終わる
②Updateより後に実行されるコルーチン内で位置、角度の補正
という流れになっているということです。

※コルーチンのUnityの実行順についてはこちらで図解してあります。

この実装の欠点

この実装にはいくつか不具合の元がありまして、既存のコンテンツに後入れで導入した場合、バグを生む可能性があります。

例えば、カメラの座標がワールド座標の○○を超えたらイベント発生、などの実装があった場合、
最初の1フレームでそのイベントが発生してしまう恐れがあります。

なぜそんなことが起きる可能性があるかというと、最初の1フレームをトラッキングに使用するので、
実際に目に見えて始まる位置(補正後の位置)とは異なる座標を1フレーム分通過してしまうからです。

なので、最初の1フレームの間、そのイベントのトリガーはオフにしておいて、
位置補正終了と同時にオンにするなどの工夫が必要です。
今回のコードにもイベントを登録できる形で組み込んでいます。

        yield return null;
        startEvent.Invoke(); //ここにトリガーをオンにするイベントを登録

また、トラッキングがMissingになった状態でUpdateの最初の1フレームを通過してしまうと
補正が正しくかからない等の問題点も残しており、まだまだ改善の余地はありそうです。

あと今回の内容はOculusコンテンツに導入しても正しく動きます。
むしろ、最初はOculus向けに作ってた機能で、VIVEにつっこんだらなんか動いたからヨシ!って感じです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【.NetCore】Html.Rawで<br>だけそのままにする。

改行文字を<br >に変換するだけでは改行されない。

安直に改行文字の\rや\nを<br >に置換すればいいと思うが、実はRazorではバインドした段階で値がHTMLエンコードされる。
よって表示時には& lt;br & gt;に変換されてしまう。

強制エンコードされてしまう以上、タグを埋め込むのは不可能なのでエンコードせずに出力してみる。

View
@Html.Raw(sample.text)

@Html.Rowメソッドを使うと、Htmlエンコードされずにそのままの値をHTMLに出力してくれる。
出力される値が固定の場合はこの方法で問題ないが、任意に変更できる場合はクロスサイトスクリプティングの危険がある。
よってこの方法は用途によっては使えない。

なんか楽しかったので2種類のパターンを考えた

その1、エンコードして改行だけデコードする。

1度エンコードして、改行文字のとこだけデコードを行う。
その値をHtml.Raw()で出力すれば<br >だけそのまま出力されるので改行される。

View
   @Html.Raw(Html.Encode(tmp.Detail).Replace("&#xD;&#xA;", "<br>"))

その2、ゴリ押し

View
    var detail = tmp.Detail.Split("\r\n");
    @foreach (string tmp in detail)
    {
        @tmp<br />
    }

これで改行ごとに<br >が挿入されるので改行できる。
HTMLエンコードなにそれ?って人はこっちでいいと思います。

その1をStaticメソッドにしとくといいかも!?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】Html.Rawで<br>だけそのままにする。【Razor】

改行文字を<br >に変換するだけでは改行されない。

安直に改行文字の\rや\nを<br >に置換すればいいと思うが、実はRazorではバインドした段階で値がHTMLエンコードされる。
よって表示時には& lt;br & gt;に変換されてしまう。

強制エンコードされてしまう以上、タグを埋め込むのは不可能なのでエンコードせずに出力してみる。

View
@Html.Raw(sample.text)

@Html.Rowメソッドを使うと、Htmlエンコードされずにそのままの値をHTMLに出力してくれる。
出力される値が固定の場合はこの方法で問題ないが、任意に変更できる場合はクロスサイトスクリプティングの危険がある。
よってこの方法は用途によっては使えない。

なんか楽しかったので2種類のパターンを考えた

その1、エンコードして改行だけデコードする。

1度エンコードして、改行文字のとこだけデコードを行う。
その値をHtml.Raw()で出力すれば<br >だけそのまま出力されるので改行される。

View
   @Html.Raw(Html.Encode(tmp.Detail).Replace("&#xD;&#xA;", "<br>"))

その2、ゴリ押し

View
    var detail = tmp.Detail.Split("\r\n");
    @foreach (string tmp in detail)
    {
        @tmp<br />
    }

これで改行ごとに<br >が挿入されるので改行できる。
HTMLエンコードなにそれ?って人はこっちでいいと思います。

その1をStaticメソッドにしとくといいかも!?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ReSharper】ReSharper の使い方に関する記事まとめ(75個)

備考

この投稿は自分のブログの記事の転載になります
http://baba-s.hatenablog.com/entry/2019/06/07/090000

はじめに

自分のブログで公開した ReSharper の使い方に関する記事を75個まとめました

おすすめ

Unity

コードクリーンナップ

自動生成

Inspect

ファイルレイアウト

ショートカットキー

オプション

トラブルシューティング

拡張機能

その他

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】謎のアサーション'IsNormalized(dir)'&'IsNormalized(ray.GetDirection())'

バージョン

Unity 2019.1.9f1

はじめに

こんなアサーションに遭遇しました。
Assertion failed on expression: 'IsNormalized(dir)'
Assertion failed on expression: 'IsNormalized(ray.GetDirection())'

クリックしてもそれ以上の情報はなく、どこに問題があるのかどころか、誰がこのアサーションを吐いているのかさえ分からず。

とりあえず調べてみると公式フォーラムでFlare Layerを切ると消えたという情報が出てきます。
切ってみると確かに出なくなりましたが、このままではレンズフレアが使えない、ということで色々試して回避できそうな方法が分かったので共有します。

再現手順

  1. 空のシーンを作成します。
  2. Directional LightにLens Flareを追加します。
  3. Main CameraにFlare Layerを追加します。

ここまではレンズフレアを使うときにやる普通の作業かと思います。
このあと下記の2パターンで確認できました。

パターン1(Orthographicカメラ)

  1. Main CameraのProjectionをOrthographicにします。
  2. 再生します。
  3. Main CameraとDirectional LightのPositionを一致させます。

パターン2(Perspectiveカメラ)

  1. Main CameraのProjectionをPerspectiveにします。
  2. 再生します。
  3. Main CameraとDirectional LightのTransformを一致させつつ、PositionとRotationを適当に動かします。 (例えばPosition: -2, -13, -10、Rotation: 77.2, 112.32, 0とか)

原因ははっきりとは分からないのですが、カメラとLens Flareの位置関係によってはレンズフレアの計算ができない状況になるのでしょう。

対処法

このアサーションに遭遇する原因として一番ありそうなのがカメラにLens Flareが付いてしまっているパターン。
この場合、Lens Flareの使い方が間違っていると思われますので、まずは使い方が合っているか確認するのがいいかと思います。
こちらこちらを参考にさせて頂くと、空のオブジェクトやLightに付けるのが正しいかと思いますので、そのようにしましょう。

それでも、再現手順でやったように、カメラとLens Flareの位置が一致する状況があれば発生してしまうことでしょう。
その場合は両者の位置関係を意識したうえで、スクリプトやオブジェクト配置を見直してみるのがいいかと思います。

おわりに

すぐに原因箇所が分からないアサーションなので、解決に時間を取られてしまわないよう、一助になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む