20190405のC#に関する記事は5件です。

FirstPersonController.cs

The script of FPSController.
No editing.

FirstPersonController.cs
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Utility;
using Random = UnityEngine.Random;

namespace UnityStandardAssets.Characters.FirstPerson
{
    [RequireComponent(typeof (CharacterController))]
    [RequireComponent(typeof (AudioSource))]
    public class FirstPersonController : MonoBehaviour
    {
        [SerializeField] private bool m_IsWalking;
        [SerializeField] private float m_WalkSpeed;
        [SerializeField] private float m_RunSpeed;
        [SerializeField] [Range(0f, 1f)] private float m_RunstepLenghten;
        [SerializeField] private float m_JumpSpeed;
        [SerializeField] private float m_StickToGroundForce;
        [SerializeField] private float m_GravityMultiplier;
        [SerializeField] private MouseLook m_MouseLook;
        [SerializeField] private bool m_UseFovKick;
        [SerializeField] private FOVKick m_FovKick = new FOVKick();
        [SerializeField] private bool m_UseHeadBob;
        [SerializeField] private CurveControlledBob m_HeadBob = new CurveControlledBob();
        [SerializeField] private LerpControlledBob m_JumpBob = new LerpControlledBob();
        [SerializeField] private float m_StepInterval;
        [SerializeField] private AudioClip[] m_FootstepSounds;    // an array of footstep sounds that will be randomly selected from.
        [SerializeField] private AudioClip m_JumpSound;           // the sound played when character leaves the ground.
        [SerializeField] private AudioClip m_LandSound;           // the sound played when character touches back on ground.

        private Camera m_Camera;
        private bool m_Jump;
        private float m_YRotation;
        private Vector2 m_Input;
        private Vector3 m_MoveDir = Vector3.zero;
        private CharacterController m_CharacterController;
        private CollisionFlags m_CollisionFlags;
        private bool m_PreviouslyGrounded;
        private Vector3 m_OriginalCameraPosition;
        private float m_StepCycle;
        private float m_NextStep;
        private bool m_Jumping;
        private AudioSource m_AudioSource;

        public bool AllowRotate;

        // Use this for initialization
        private void Start()
        {
            m_CharacterController = GetComponent<CharacterController>();
            m_Camera = Camera.main;
            m_OriginalCameraPosition = m_Camera.transform.localPosition;
            m_FovKick.Setup(m_Camera);
            m_HeadBob.Setup(m_Camera, m_StepInterval);
            m_StepCycle = 0f;
            m_NextStep = m_StepCycle/2f;
            m_Jumping = false;
            m_AudioSource = GetComponent<AudioSource>();
            m_MouseLook.Init(transform , m_Camera.transform);
        }


        // Update is called once per frame
        private void Update()
        {
            if (AllowRotate)
            {
                RotateView();
            }
            // the jump state needs to read here to make sure it is not missed
            if (!m_Jump)
            {
                m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
            }

            if (!m_PreviouslyGrounded && m_CharacterController.isGrounded)
            {
                StartCoroutine(m_JumpBob.DoBobCycle());
                PlayLandingSound();
                m_MoveDir.y = 0f;
                m_Jumping = false;
            }
            if (!m_CharacterController.isGrounded && !m_Jumping && m_PreviouslyGrounded)
            {
                m_MoveDir.y = 0f;
            }

            m_PreviouslyGrounded = m_CharacterController.isGrounded;
        }


        private void PlayLandingSound()
        {
            m_AudioSource.clip = m_LandSound;
            m_AudioSource.Play();
            m_NextStep = m_StepCycle + .5f;
        }


        private void FixedUpdate()
        {
            float speed;
            GetInput(out speed);
            // always move along the camera forward as it is the direction that it being aimed at
            Vector3 desiredMove = transform.forward*m_Input.y + transform.right*m_Input.x;

            // get a normal for the surface that is being touched to move along it
            RaycastHit hitInfo;
            Physics.SphereCast(transform.position, m_CharacterController.radius, Vector3.down, out hitInfo,
                               m_CharacterController.height/2f, Physics.AllLayers, QueryTriggerInteraction.Ignore);
            desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized;

            m_MoveDir.x = desiredMove.x*speed;
            m_MoveDir.z = desiredMove.z*speed;


            if (m_CharacterController.isGrounded)
            {
                m_MoveDir.y = -m_StickToGroundForce;

                if (m_Jump)
                {
                    m_MoveDir.y = m_JumpSpeed;
                    PlayJumpSound();
                    m_Jump = false;
                    m_Jumping = true;
                }
            }
            else
            {
                m_MoveDir += Physics.gravity*m_GravityMultiplier*Time.fixedDeltaTime;
            }
            m_CollisionFlags = m_CharacterController.Move(m_MoveDir*Time.fixedDeltaTime);

            ProgressStepCycle(speed);
            UpdateCameraPosition(speed);

            m_MouseLook.UpdateCursorLock();
        }


        private void PlayJumpSound()
        {
            m_AudioSource.clip = m_JumpSound;
            m_AudioSource.Play();
        }


        private void ProgressStepCycle(float speed)
        {
            if (m_CharacterController.velocity.sqrMagnitude > 0 && (m_Input.x != 0 || m_Input.y != 0))
            {
                m_StepCycle += (m_CharacterController.velocity.magnitude + (speed*(m_IsWalking ? 1f : m_RunstepLenghten)))*
                             Time.fixedDeltaTime;
            }

            if (!(m_StepCycle > m_NextStep))
            {
                return;
            }

            m_NextStep = m_StepCycle + m_StepInterval;

            PlayFootStepAudio();
        }


        private void PlayFootStepAudio()
        {
            if (!m_CharacterController.isGrounded)
            {
                return;
            }
            // pick & play a random footstep sound from the array,
            // excluding sound at index 0
            int n = Random.Range(1, m_FootstepSounds.Length);
            m_AudioSource.clip = m_FootstepSounds[n];
            m_AudioSource.PlayOneShot(m_AudioSource.clip);
            // move picked sound to index 0 so it's not picked next time
            m_FootstepSounds[n] = m_FootstepSounds[0];
            m_FootstepSounds[0] = m_AudioSource.clip;
        }


        private void UpdateCameraPosition(float speed)
        {
            Vector3 newCameraPosition;
            if (!m_UseHeadBob)
            {
                return;
            }
            if (m_CharacterController.velocity.magnitude > 0 && m_CharacterController.isGrounded)
            {
                m_Camera.transform.localPosition =
                    m_HeadBob.DoHeadBob(m_CharacterController.velocity.magnitude +
                                      (speed*(m_IsWalking ? 1f : m_RunstepLenghten)));
                newCameraPosition = m_Camera.transform.localPosition;
                newCameraPosition.y = m_Camera.transform.localPosition.y - m_JumpBob.Offset();
            }
            else
            {
                newCameraPosition = m_Camera.transform.localPosition;
                newCameraPosition.y = m_OriginalCameraPosition.y - m_JumpBob.Offset();
            }
            m_Camera.transform.localPosition = newCameraPosition;
        }


        private void GetInput(out float speed)
        {
            // Read input
            float horizontal = CrossPlatformInputManager.GetAxis("Horizontal");
            float vertical = CrossPlatformInputManager.GetAxis("Vertical");

            bool waswalking = m_IsWalking;

#if !MOBILE_INPUT
            // On standalone builds, walk/run speed is modified by a key press.
            // keep track of whether or not the character is walking or running
            m_IsWalking = !Input.GetKey(KeyCode.LeftShift);
#endif
            // set the desired speed to be walking or running
            speed = m_IsWalking ? m_WalkSpeed : m_RunSpeed;
            m_Input = new Vector2(horizontal, vertical);

            // normalize input if it exceeds 1 in combined length:
            if (m_Input.sqrMagnitude > 1)
            {
                m_Input.Normalize();
            }

            // handle speed change to give an fov kick
            // only if the player is going to a run, is running and the fovkick is to be used
            if (m_IsWalking != waswalking && m_UseFovKick && m_CharacterController.velocity.sqrMagnitude > 0)
            {
                StopAllCoroutines();
                StartCoroutine(!m_IsWalking ? m_FovKick.FOVKickUp() : m_FovKick.FOVKickDown());
            }
        }


        private void RotateView()
        {
            m_MouseLook.LookRotation (transform, m_Camera.transform);
        }


        private void OnControllerColliderHit(ControllerColliderHit hit)
        {
            Rigidbody body = hit.collider.attachedRigidbody;
            //dont move the rigidbody if the character is on top of it
            if (m_CollisionFlags == CollisionFlags.Below)
            {
                return;
            }

            if (body == null || body.isKinematic)
            {
                return;
            }
            body.AddForceAtPosition(m_CharacterController.velocity*0.1f, hit.point, ForceMode.Impulse);
        }
    }
}

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

テキスト入力中に選択候補を一覧表示して入力効率化! [XamMultiColumnComboEditor]

はじめに

Gooogleの検索候補、IMEなど、入力中に表示される予測変換の機能は便利ですよね。
今回紹介する XamMultiColumnComboEditor は、同様の機能が実装でき、さらにオプション機能の設定により様々な表現が可能です。
例えば、後ほど紹介するサンプルアプリケーションでは、都道府県の検索を例に、漢字での検索、ひらがなでの検索、ローマ字での予測入力を同時に対応することが可能です。

アプリケーションサンプル

都道府県の入力を例に、漢字、ひらがな、ローマ字。いずれの入力でも選択候補が表示されるサンプルをご用意しました。
漢字、ひらがな、ローマ字のいずれでもフィルタリングができ、また選択候補から選択された場合に採用されるのは漢字の都道府県となります。(フィルタの対象や、採用される項目の変更などはオプションで選択可能です。)
Capture.gif

サンプルコード

GitHub
https://github.com/furugen/wpf-samples-keyboard-tips/tree/sample1

MainWindow.xaml
<Window  x:Class="wpf_samples_keyboard_tips.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:wpf_samples_keyboard_tips"
        xmlns:ig="http://schemas.infragistics.com/xaml"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="comboItemTemplate" >
            <TextBlock Text="{Binding Path=Name}"></TextBlock>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal" Height="30">
                <TextBlock>
                都道府県
                </TextBlock>
                <ig:XamMultiColumnComboEditor Width="300" DisplayMemberPath="Name" x:Name="xamComboEditor" ItemsSource="{Binding SampleData}" FilterMode="FilterOnAllColumns" >
                    <ig:XamMultiColumnComboEditor.Columns>
                        <ig:TextComboColumn Key="Name" HeaderText="都道府県"></ig:TextComboColumn>
                        <ig:TextComboColumn Key="Hiragana" HeaderText="都道府県(かな)"></ig:TextComboColumn>
                        <ig:TextComboColumn Key="Romaji" HeaderText="都道府県(ローマ字)"></ig:TextComboColumn>
                    </ig:XamMultiColumnComboEditor.Columns>
                </ig:XamMultiColumnComboEditor>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>
SampleData.cs
    public class SampleData
    {
        public string Name { get; set; }
        public string Hiragana { get; set; }
        public string Romaji { get; set; }
    }
MainWindowViewModel.cs
   public class MainWindowViewModel : INotifyPropertyChanged
    {
        public MainWindowViewModel()
        {
            #region サンプルデータを設定
            this.SampleData = new ObservableCollection<SampleData>();
            this.SampleData.Add(new SampleData() { Name = "北海道", Hiragana = "ほっかいどう", Romaji = "hokkaido" });
            this.SampleData.Add(new SampleData() { Name = "青森", Hiragana = "あおもり", Romaji = "aomori" });
            this.SampleData.Add(new SampleData() { Name = "岩手", Hiragana = "いわて", Romaji = "iwate" });
            this.SampleData.Add(new SampleData() { Name = "宮城", Hiragana = "みやぎ", Romaji = "miyagi" });
            this.SampleData.Add(new SampleData() { Name = "秋田", Hiragana = "あきた", Romaji = "akita" });
            this.SampleData.Add(new SampleData() { Name = "山形", Hiragana = "やまがた", Romaji = "yamagata" });
            this.SampleData.Add(new SampleData() { Name = "福島", Hiragana = "ふくしま", Romaji = "fukushima" });
            this.SampleData.Add(new SampleData() { Name = "茨城", Hiragana = "いばらき", Romaji = "ibaraki" });
            this.SampleData.Add(new SampleData() { Name = "栃木", Hiragana = "とちぎ", Romaji = "tochigi" });
            this.SampleData.Add(new SampleData() { Name = "群馬", Hiragana = "ぐんま", Romaji = "gunma" });
            this.SampleData.Add(new SampleData() { Name = "埼玉", Hiragana = "さいたま", Romaji = "saitama" });
            this.SampleData.Add(new SampleData() { Name = "千葉", Hiragana = "ちば", Romaji = "chiba" });
            this.SampleData.Add(new SampleData() { Name = "東京", Hiragana = "とうきょう", Romaji = "tokyo" });
            this.SampleData.Add(new SampleData() { Name = "神奈川", Hiragana = "かながわ", Romaji = "kanagawa" });
            this.SampleData.Add(new SampleData() { Name = "新潟", Hiragana = "にいがた", Romaji = "niigata" });
            this.SampleData.Add(new SampleData() { Name = "富山", Hiragana = "とやま", Romaji = "toyama" });
            this.SampleData.Add(new SampleData() { Name = "石川", Hiragana = "いしかわ", Romaji = "ishikawa" });
            this.SampleData.Add(new SampleData() { Name = "福井", Hiragana = "ふくい", Romaji = "fukui" });
            this.SampleData.Add(new SampleData() { Name = "山梨", Hiragana = "やまなし", Romaji = "yamanashi" });
            this.SampleData.Add(new SampleData() { Name = "長野", Hiragana = "ながの", Romaji = "nagano" });
            this.SampleData.Add(new SampleData() { Name = "岐阜", Hiragana = "ぎふ", Romaji = "gifu" });
            this.SampleData.Add(new SampleData() { Name = "静岡", Hiragana = "しずおか", Romaji = "shizuoka" });
            this.SampleData.Add(new SampleData() { Name = "愛知", Hiragana = "あいち", Romaji = "aichi" });
            this.SampleData.Add(new SampleData() { Name = "三重", Hiragana = "みえ", Romaji = "mie" });
            this.SampleData.Add(new SampleData() { Name = "滋賀", Hiragana = "しが", Romaji = "shiga" });
            this.SampleData.Add(new SampleData() { Name = "京都", Hiragana = "きょうと", Romaji = "kyoto" });
            this.SampleData.Add(new SampleData() { Name = "大阪", Hiragana = "おおさか", Romaji = "osaka" });
            this.SampleData.Add(new SampleData() { Name = "兵庫", Hiragana = "ひょうご", Romaji = "hyogo" });
            this.SampleData.Add(new SampleData() { Name = "奈良", Hiragana = "なら", Romaji = "nara" });
            this.SampleData.Add(new SampleData() { Name = "和歌山", Hiragana = "わやかま", Romaji = "wakayama" });
            this.SampleData.Add(new SampleData() { Name = "鳥取", Hiragana = "とっとり", Romaji = "tottori" });
            this.SampleData.Add(new SampleData() { Name = "島根", Hiragana = "しまね", Romaji = "shimane" });
            this.SampleData.Add(new SampleData() { Name = "岡山", Hiragana = "おかやま", Romaji = "okayama" });
            this.SampleData.Add(new SampleData() { Name = "広島", Hiragana = "ひろしま", Romaji = "hiroshima" });
            this.SampleData.Add(new SampleData() { Name = "山口", Hiragana = "やまぐち", Romaji = "yamaguchi" });
            this.SampleData.Add(new SampleData() { Name = "徳島", Hiragana = "とくしま", Romaji = "tokushima" });
            this.SampleData.Add(new SampleData() { Name = "香川", Hiragana = "かがわ", Romaji = "kagawa" });
            this.SampleData.Add(new SampleData() { Name = "愛媛", Hiragana = "えひめ", Romaji = "ehime" });
            this.SampleData.Add(new SampleData() { Name = "高知", Hiragana = "こうち", Romaji = "kochi" });
            this.SampleData.Add(new SampleData() { Name = "福岡", Hiragana = "ふくおか", Romaji = "fukuoka" });
            this.SampleData.Add(new SampleData() { Name = "佐賀", Hiragana = "さが", Romaji = "saga" });
            this.SampleData.Add(new SampleData() { Name = "長崎", Hiragana = "ながさき", Romaji = "nagasaki" });
            this.SampleData.Add(new SampleData() { Name = "熊本", Hiragana = "くまもと", Romaji = "kumamoto" });
            this.SampleData.Add(new SampleData() { Name = "大分", Hiragana = "おおいた", Romaji = "oita" });
            this.SampleData.Add(new SampleData() { Name = "宮崎", Hiragana = "みやざき", Romaji = "miyazaki" });
            this.SampleData.Add(new SampleData() { Name = "鹿児島", Hiragana = "かごしま", Romaji = "kagoshima" });
            this.SampleData.Add(new SampleData() { Name = "沖縄", Hiragana = "おきなわ", Romaji = "okinawa" });
            #endregion 
        }


        private ObservableCollection<SampleData> _SampleData = new ObservableCollection<SampleData>();
        public ObservableCollection<SampleData> SampleData
        {
            get { return _SampleData; }
            set { _SampleData = value; OnPropertyChanged(); }
        }

        /// <summary>
        /// プロパティ値の変更をクライアントに通知する。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// PropertyChanged イベント を発生させる。
        /// </summary>
        /// <param name="propertyName">変更されたプロパティ名</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

サンプルコードの解説

まず都道府県を例にサンプルのデータを作成しています。

SampleData.cs
    public class SampleData
    {
        public string Name { get; set; }
        public string Hiragana { get; set; }
        public string Romaji { get; set; }
    }

これらの要素をXamMultiColumnComboEditorで下記のように指定して表示しています。

MainWindow.xaml
<ig:XamMultiColumnComboEditor Width="300" DisplayMemberPath="Name" x:Name="xamComboEditor" ItemsSource="{Binding SampleData}" FilterMode="FilterOnAllColumns" >
    <ig:XamMultiColumnComboEditor.Columns>
        <ig:TextComboColumn Key="Name" HeaderText="都道府県"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Hiragana" HeaderText="都道府県(かな)"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Romaji" HeaderText="都道府県(ローマ字)"></ig:TextComboColumn>
    </ig:XamMultiColumnComboEditor.Columns>
</ig:XamMultiColumnComboEditor>

DisplayMemberPath

テキストボックスで表示するプロパティパスを指定してます。表示される場所は下記の画像の赤枠部分。
Nameを指定、SampleData.Nameのプロパティが採用され、例えば、北海道のデータの場合は"北海道"と表示されます。
image.png

ItemsSource

選択候補として表示する一覧を指定します。

FilterMode = FilterOnAllColumns

選択候補に表示している列がフィルタリングの候補となります。今回のサンプルでは、都道府県名の漢字、ひらがな、ローマ字が検索対象ですね。
プライマリ列のみフィルタリング対象とするFilterOnPrimaryColumnOnlyの列挙値もあります。
https://jp.infragistics.com/help/wpf/xammulticee-configuring-item-filtering

Columnsとig:TextComboColumn

Columsnにて表示したいプロパティのカラム列を記載しています。
今回は、SampleDataのName(漢字),Hiragana(ひらがな),Romaji(ローマ字)の3つの要素を表示したいので3つのカラムを定義しています。Keyにはバインディング対象のプロパティを、HeaderTextには列の先頭に表示するヘッダーの文字列を記載しています。

MainWindow.xaml
    <ig:XamMultiColumnComboEditor.Columns>
        <ig:TextComboColumn Key="Name" HeaderText="都道府県"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Hiragana" HeaderText="都道府県(かな)"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Romaji" HeaderText="都道府県(ローマ字)"></ig:TextComboColumn>
    </ig:XamMultiColumnComboEditor.Columns>

まとめ

如何でしたでしょうか。今回のサンプルアプリケーションのように、フィルタリングは複数候補(漢字、ひらがな、ローマ字)だけど最終的に採用する文字列は1つだけ(都道府県の漢字)などは日本のソフトウェアではよくあるシナリオですが、対応しているコントロールはなかなか少ないかと思います。この記事が参考になれば幸いでございます。

次は、今回の記事で紹介した要素や、入力機能のノウハウをふんだんに取り入れた、快適な入力画面のサンプルをご紹介したいと思います!

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

【Visual Studio2019】へアップデートする

Visual Studio 2019が本格リリースされました!
さっそくアップデートしようとしましたが、少し調べる必要があったのでやり方をまとめます。

新規インストールはこちらから
https://visualstudio.microsoft.com/ja/vs/mac/

環境

macOS High Sierra 10.13.6

アップデート手順

公式

https://docs.microsoft.com/en-us/visualstudio/mac/update?view=vsmac-2019

公式読むのめんどい人向け

ここからできます
ScreenShot 2019-04-05 0.48.33.png

更新チャンネルを「Stable」にして更新プログラムを確認しましょう。
ScreenShot 2019-04-05 0.49.35.png

あとは自動で進むと思います。

おわり

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

C# NLog チュートリアル

0. 本稿について

NLogのチュートリアルを踏襲していく

1. 環境 ~Environment~

  • Microsoft Visual Studio 2017 Community
  • .Net Framework 4.6.1
  • NLog 4.6.1
  • NLog.config 4.6.1

2. 導入 ~Installing NLog~

NuGetパッケージの管理から「NLog」,「NLog.Config」をインストールする。
「NLog.Config」「NLog」も同梱されているのでこのパッケージだけで問題ない。

Install-Package NLog.Config

3. NLogの設定 ~Configure NLog Targets for output~

Configファイルで設定する方法と、ソースコードで設定する方法を記載した。どちらも同じ設定となる。本稿では設定ファイルでの方法を採用している。NLog.configパッケージをインストールすると、プロジェクトディレクトリにNLog.configファイルが作成されているので、内容を下記の通り書き換える。

設定ファイルの場合

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Debug" writeTo="logfile" />
  </rules>
</nlog>

ソースコードの場合

var config = new NLog.Config.LoggingConfiguration();

var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "file.txt" };
var logconsole = new NLog.Targets.ConsoleTarget("logconsole");

config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
config.AddRule(LogLevel.Debug, LogLevel.Fatal, logfile);

NLog.LogManager.Configuration = config;

4. ログファイルの書き出し(Writing log messages)

NLogをラップするNLogServiceクラスを作成し、ログレベル"Info"のログを出力してみる。

NLogService.cs
using NLog;

namespace NLogService
{
    public static class NLogService
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public static void PrintInfoLog(string str)
        {
            logger.Info(str);
        }
    }
}
Program.cs
namespace NLogService
{
    class Program
    {
        static void Main(string[] args)
        {
            NLogService.PrintInfoLog("Hello World");
            Console.ReadKey();
        }
    }
}

[結果]
コンソールに下記のログメッセージが出力された。また、同じログメッセージを記載したfile.txtも出力された。

2019-04-01 23:19:07.5302|INFO|NLogService.NLogService|Hello World

4-1. Loggerの指定

上記の通り、NLog.LogManager.GetCurrentClassLogger()を使用した場合、呼び出し側のクラス名でロガーを作成する。NLog.LogManager.GetLogger( "MyLogger")を呼び出した場合、明示的なロガーの名前を指定することも出来る。

下記のように、NLog.congigファイルを書き換えてみる。

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="LEVEL_INFO" minlevel="Info" writeTo="logconsole" />
    <logger name="LEVEL_DEBUG" minlevel="Debug" writeTo="logfile" />
  </rules>
</nlog>
Program.cs
namespace NLogService
{
    class Program
    {
        static void Main(string[] args)
        {
            var logger = NLog.LogManager.GetLogger("LEVEL_INFO");
            logger.Info("Hello_World");
            Console.ReadKey();
        }
    }
}

[結果]
コンソールに下記のログメッセージは表示されたが、ログファイルは出力されなかった。

4-2. minLevelについて

minLevelで設定したログレベル以上のログメッセージを出力する。1つのロガーに書き込まれたメッセージは、logging-rules設定に基づいて複数のターゲットに出力する。下記の設定ファイルの場合、ログレベルInfo以上の情報はコンソールに出力し、ログレベルError以上の情報はログファイルに出力する。

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <targets>
    <target name="logfile" xsi:type="File" fileName="file.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Error" writeTo="logfile" />
  </rules>
</nlog>

4-3. Log levels

ログレベルは、メッセージの重要度や詳細度を示します。

  • Trace
    • プロトコルのペイロードなど大量で詳細なデータを出力するときに使用する。開発中のみ有効
  • Debug
    • Traceレベルよりも詳細ではないデバック中のログを出力するときに使用する。開発中のみ有効
  • Info
    • 情報メッセージ。稼働環境で有効
  • Warn
    • 警告メッセージ。回復可能であるか、または一時的な障害に関する警告メッセージを出力する。
  • Error
    • エラーメッセージ。Exseption情報を出力する。
  • Fatal
    • 非常に重大なエラーメッセージ。
MyClass.cs
public class MyClass
{
  private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

  public void MyMethod1()
  {
    //各ログレベルの出力サンプル
    logger.Trace("Sample trace message");
    logger.Debug("Sample debug message");
    logger.Info("Sample informational message");
    logger.Warn("Sample warning message");
    logger.Error("Sample error message");
    logger.Fatal("Sample fatal error message");

    //またはLog()メソッドにログレベルとメッセージを渡すことで出力することが可能
    logger.Log(LogLevel.Info, "Sample informational message");


    // Exseption情報を出力する例
    try
    {
        //do something
    }
    catch (Exception ex)
    {
        logger.Error(ex, "ow noos!"); // render the exception with ${exception}
        throw;
    }
  }
}

4-4. レイアウトとレイアウトレンダリング(Layouts and LayoutRenderers)

ログメッセージのレイアウトを設定することができる。下記のプログラムで出力するメッセージを比較する。

Program.cs
namespace NLogService
{
    class Program
    {
        static void Main(string[] args)
        {
            NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
            Exception ex = new Exception();
            logger.Error(ex);
        }
    }
}

4-4-1. デフォルトのシンプルレイアウト

<target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />

4-4-2. 出力メッセージ

2019-04-02 21:28:01.3018|ERROR|NLogService.Program|System.Exception: 種類 'System.Exception' の例外がスローされました。

4-4-3. 詳細なログレイアウト

<target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level:uppercase=true}|${logger}|${threadid}|${message}|${exception:format=tostring}" />

4-4-4. 出力メッセージ

2019-04-02 21:27:19.1566|ERROR|NLogService.Program|1|System.Exception: 種類 'System.Exception' の例外がスローされました。|System.Exception: 種類 'System.Exception' の例外がスローされました。

他にもCSVJSONでのフォーマットが存在する。

5.効率の良いロギング ~Best Practices for Logging~

5-1. ~Logger should be a static variable in each class~

ロガーは各クラスの静的変数であるべきです。新しくロガーを作成することでロックを取得してオブジェクトを割り当てる必要があるので、無駄な処理となります。そのため、下記のようにロガーをあらかじめ作成することが効率が良いです。

namespace MyNamespace
{
  public class MyClass
  {
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
  }
}

5-2. ~Logger should handle string formatting~

ロガーに文字列フォーマットを操作させてください。文字列の連結やフォーマット処理を直前に行わないでください。代わりにLoggerにフォーマット処理をさせてください。これにより無駄な処理を減らすことができます。下記のように実行してください。

logger.Info("Hello {0}", "Earth");

5-3. ~Logger should be given the Exception~

ロガーに例外を与えてください。ロガーに例外をフォーマットのパラメータとして与えないでください。代わりに下記のように、最初の引数として与えて下さい。

try
{
}
catch (Exception ex)
{
    logger.Error(ex, "Something bad happened");
}

5-4. Validate XML configuration from NLog.config

NLogはデフォルトですべての例外を飲み込むので、ログの出力中に問題が発生してもアプリケーションを終了することはありません。しかし、初期のNLogの設定に失敗した場合は、ロキング出来なくなるのでアプリケーションにとって致命的な問題です。そこで、throwConfigExceptions = "true"をNLog.Configに追加することで、設定に問題があった時にエラーを発生することが出来ます。

NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwConfigExceptions="true">
</nlog>

throwExceptions = "true"という設定もありますがこれは本番環境では使用しないでください。アプリケーションに致命的な問題を与える可能性があります。これは、単体テストとローカルのトラブルシューティングを目的としています。

5-5 Remember to Flush

手動でクローズしてください。NLogはデフォルトでアプリケーションのシャットダウン時に自動的にクローズしようとします。Microsoft Windowsが提供する.NETアプリケーションではアプリケーションが終了するまでの時間(通常2秒)以内にシャットダウンすることが出来ます。ネットワークトラフィック(Http,Mail,Tcp)を利用するNLog設定の場合は、手動でクローズ/シャットダウンすることをお勧めします。

NLog.LogManager.Shutdown(); // Flush and close down internal threads and timers

Mono/Linux上で実行している.NETアプリケーションではアプリケーションのシャットダウン時に上記の停止処理をする必要があります。処理を行わない場合、未処理の例外やセグメンテーション違反、その他予測不可能な動作が発生する可能性があります。

6. ラッパー ~Wripper~

NLogではラッパーを設定することが出来ます。よく使われるWrapperは下記の通りです。

  • AsyncWrapper
    • 書き込みが非同期となる。
  • BufferingWrapper
    • ログをバッファでまとめて出力する。
  • FallbackGroup
    • エラー時にフォールバック機能を提供できる。
  • RetryingWrapper
    • 書き込みに失敗したら、再度書き込みを行う。

下記はAsyncWrapperの短縮形「async="true"」のサンプルとなります。
これによって、ファイルへのすべての書き込みが非同期となります。

NLog.Config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <targets async="true">
        <target name="logfile" xsi:type="File" fileName="file.txt" />
    </targets>

    <rules>
        <logger name="*" minlevel="Info" writeTo="logfile" />
    </rules>
</nlog>

99. 参考

NLog Home

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

ASP.NET Coreのタグへルパを自作してViewModelの値を出力する

何をしたいのか

任意のcshtmlが以下のようなモデルを@modelで指定されていたとする。

public class SampleViewModel
{
    public int TheAnswer { get; set; } = 42;
}

で、これを以下のようなタグをcshtmlに指定すると

<display-value asp-for="TheAnswer"></display-value>

下記のように書き換えてくれる

<div>TheAnswer:<strong>  42</strong></div>

書き換えるタグへルパを作ると起動すれば良いのか備忘録的に。

但し、Asp.net coreを始めて3日目くらいなので、何かと至らない点が多々有るかと。。。
なので、何かあればご指定期頂ければこれ幸い。

タグへルパを作る

タグへルパは以下の通り

    public class ValueDisplayTagHelper:TagHelper
    {
        [HtmlAttributeName("asp-for")]
        public ModelExpression Target { get; set; }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = "div";

            output.Content.SetHtmlContent($@"{Target.Name}:<strong> {Target.Model}</strong>");
        }
    }

Targetの別名をasp-forにすることで、cshtml側でインテリセンスが効く。

cshtml側での仕込み

アセンブリの名前が、WebApplication3として自作タグへルパのスコープを有効にするには、

@addTagHelper *, WebApplication3

と記載するして、アセンブリ内の全てのタグへルパを有効化するか、厳密に単一のタグへルパのみ有効にするなら

@addTagHelper WebApplication3.TagHelpers.ValueDisplayTagHelper,WebApplication3

と記載する。

タグの使用方法

ここまで出来たら、以下のように書くことで、ViewModelを表示できる

@model SampleViewModel

@{
    ViewData["Title"] = "Home Page";
}

<value-display asp-for="TheAnswer"></value-display>


参考にしたページ

ASP.NET Core のタグ ヘルパー
How to create TagHelper who's value is a Model Property (without using @Model)?
First Dynamic Form Tag Helper Attempt - Tech Dominator

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