うさ☆うさ日記 このページをアンテナに追加 RSSフィード

2006 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 05 | 06 |
2013 | 05 | 08 | 09 | 10 | 11 | 12 |
2014 | 08 |
2015 | 04 | 06 | 08 | 09 | 11 | 12 |
2016 | 01 | 02 |

2016-02-02

[]未だにCompact Frameworkの開発とかしてるやつ

とりあえずコードの断捨離として、GitHubに旧コードをこっそり置いてあったりします(・ω・)

https://github.com/usausa/Net-Smart-CE

概要

  • ぎょーむアプリを作る際に使用するCompact Frameworkの機能を補うためのものです
  • SDF(Smart Device Framework)とかを使っているのであればいらないかもしれません
  • もともと、SDFの一部前身がMSDNの記事にあったりしたころから、それを参考に作った機能もあるので
  • CE用Smart.Navigationの最新版も含まれています(この版、あまり気に入っていないけど)
  • 実は完全版ではありません、以下の機能は気が向いたらFixして追加するかもしれません
  • HT開発にありがちな固定長ファイル操作、一覧表示用Control、通信系機能は、実装が複数あったり不完全な箇所があったりするので除外しています
  • Reactive、Functionalな機能も、同様の理由で除外しています、好き嫌いもあるし

まあ、気が向いたら手を入れるかも(´・ω・`)

2016-01-29

[]WPFでWindowStyle="None" ResizeMode="NoResize"なWindowを作るとドロップシャドウがつかなくなる件について

WPF小ネタ。


っで、この件に関するお手軽な対策としては、Border.EffectにDropShadowEffectを使用してWindowの縁部分のドロップシャドウを描画すること。

このためには、Window自体は透明にして、内部のGridに背景色を設定します。


処理を抜粋するとこんな感じ。

<Window x:Class="Example.WpfApplication.Views.HogeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="OperationWindow"
        AllowsTransparency="True" WindowStyle="None" ResizeMode="NoResize" BorderThickness="3">
    <Border>
        <Border.Effect>
            <DropShadowEffect BlurRadius="5" Color="Black" Opacity="0.8" ShadowDepth="0.5" />
        </Border.Effect>
        <Grid Background="White">
...
        </Grid>
    </Border>
</Window>

っで、サンプルソースを以下においておきます。

https://github.com/usausa/Example-Net-WPF-BorderlessWindow


このサンプルでは、MainWindowのタイトルバーにある[Operation]、[Result]ボタンを押すと、それぞれDropShadowEffectのついたWindowStyle="None" ResizeMode="NoResize"な子Windowが表示されます。

f:id:machi_pon:20160129113534p:image

2016-01-11

[]InspectCode(ReSharper Command Line Tools)をJenkinsで使う

あまりInspectCodeの話を見ないので書いておきます(・ω・)

入手先は以下。

https://www.jetbrains.com/resharper/features/command-line.html

これはなんぞ?

ReSharperコマンドラインで実行できるツールです。

実行結果をファイルに保存し、その内容をJenkinsで表示したり出来ます。

単体での使い方

以下のように実行すると、results.xmlに実行結果が保存されます。

InspectCode.exe Example.sln /o=results.xml /no-swea

オプションの詳細は下記あたりを参照してください。

https://confluence.jetbrains.com/display/NETCOM/Introducing+InspectCode

Jenkinsでの使い方

警告の表示にはWarnings Pluginを使用します。

設定としては、まず[ビルド]から[Windowsバッチコマンドの実行]でIncpectCode.exeの実行を行います。

次に、[ビルド後の処理]で[コンパイラの警告の集計]を追加し、[ワークスペースのファイルをスキャンする]で[集計するファイル]にInspectCodeの出力ファイルを設定、[パーサー]には「Resharper InspectCode」を選択します。

f:id:machi_pon:20160111164455p:image

っで、ビルド実行がされると、IncpectCodeの結果も集計されるようになります。

f:id:machi_pon:20160111164456p:image

.NETアプリの静的チェックについては、StyleCop.Analyzersによるコードスタイルのチェック、コード分析によるチェックに加え、InspectCodeによるチェックが基本三点セットというところでしょうか(・ω・)?

2015-12-18

[]ウエッブ屋さんなんかにはまったく関係ないけどエッスアイヤーではまだお金も貰えたりもする情報処理技術者試験、ST取ってコンプ(・ω・)?

正直、もうモチベーションも糞もなかったんですが(´・ω・`)ITストラテジスト合格して、コンプしたので一応、完結編パピコ。

まあ、正確に言うと自分が持っているのはSCではなく昔のSVなんですが、会社からお金が貰えないという意味では同じもの扱いしておきます。


っで、過去編インデックスは以下。

中身がある日記はSAのくらいですが。

ST自体について?

とりあえず、普段コードよりもパワポを書いている人の方が受かりやすいんじゃないでしょうか(・ω・)?、つって。


あと、どうせなのでついでにこれも。

わい、ベンダーの試験ってこれしか受験したことない(・ω・;)

2015-11-13

[]Windows.Devices.SmartCards名前空間のクラスとPaSoRiFeliCaカードを使う(`・ω・´)

Windows 10上で動くUWPアプリからWindows.Devices.SmartCards名前空間のクラスを用いて、PaSoRiFeliCaカードを使う方法について。

使うPaSoRiはこれ。

SONY 非接触ICカードリーダー/ライター PaSoRi(パソリ) USB対応 RC-S380

SONY 非接触ICカードリーダー/ライター PaSoRi(パソリ) USB対応 RC-S380

っで、リーダーに送信するコマンドの構築を容易にするために、下記を利用するのでソースをダウンロードしておきます。

NFC Smart Card Reader PC/SC Library


上記の準備ができたら、Visual Studio 2015でUWPのプロジェクトを作成。

PC/SC Libraryのソースについては、とりあえずPcScSdkフォルダのソースを自分のプロジェクトに追加しちゃいましょうか。

また、プロジェクトから「Package.appxmanifest」を開き、[機能]の[共有ユーザー証明書]をチェックしておきます。


っで、ここまでできればPaSoRiFeliCaカードを使う準備は完了、後は以下のようなコードでOKだよもん。

using Windows.Devices.Enumeration;
using Windows.Devices.SmartCards;
using Windows.Networking.Proximity;

using Felica;

private async void Polling()
{
    // Reader検索
    var selector = SmartCardReader.GetDeviceSelector(SmartCardReaderKind.Any);
    var devices = await DeviceInformation.FindAllAsync(selector);
    var device = devices.FirstOrDefault();
    if (device == null)
    {
        return;
    }

    var reader = await SmartCardReader.FromIdAsync(device.Id);
    if (reader == null)
    {
        return;
    }

    // カード検索
    var cards = await reader.FindAllCardsAsync();
    var card = cards.FirstOrDefault();
    if (card == null)
    {
        return;
    }

    // 接続してポーリングコマンド送信
    using (var con = await card.ConnectAsync())
    {
        var handler = new AccessHandler(con);

        var result = await handler.TransparentExchangeAsync(new byte[] { 6, 0, 0xff, 0xff, 0, 3 });
    }
}

送ってるコマンドは特に説明しないので知らない人はFeliCaの資料でも見てくんろ。

っで、ポーリングが成功すれば、resultには18byte(サイズ 1byte + コマンド 1byte + IDm 8byte + PMm 8byte)の応答が返ってきます。


楽ちんぽ(・∀・)


なお、UWPアプリじゃなくてデスクトップアプリでも、TargetPlatformVersionを設定してWindows.winmdとSystem.Runtime.WindowsRuntime.dllを参照すればWindows.Devices.SmartCardsの使用は可能で、その場合は「Package.appxmanifest」の部分の作業は不要です。

2015-11-12

[]Windows 10 IoTRaspberry Pi 2でUSB温度センサーを使…えなかった件(´・ω・`)

失敗談メモ。

やろうとしたことはタイトルどおり。


使おうと思った温度センサーは、非Windows 10 IoTRaspberry Piではよく使われているこれ。

USB温度計! USB thermometer-528018

USB温度計! USB thermometer-528018

っで、UWPなプログラムを作り、以下のような処理でHIDとして値を取得しようとしてみたですよ(・ω・)

var selector = HidDevice.GetDeviceSelector(0x01, 0x06, 0x0C45, 0x7401);
var deviceInformations = await DeviceInformation.FindAllAsync(selector);
var di = deviceInformations.FirstOrDefault();
if (deviceInformation != null)
{
    var hidDevice = await HidDevice.FromIdAsync(deviceInformation.Id, FileAccessMode.ReadWrite);
    if (hidDevice != null)
    {
        ...

deviceInformationは取得できる(Name=TEMPerV1.4)ものの、hidDeviceが取得できずにnullになる。

あっ、manifestファイルにDeviceCapability書くの忘れてた( ゚д゚)、っということで以下のようなエントリを追加してみて。

<DeviceCapability Name="humaninterfacedevice">
  <Device Id="vidpid:0C45 7401">
    <Function Type="usage:0001 0006"/>
  </Device>
</DeviceCapability>

っで、Visual Studio上でデバッグ実行をしようとしたところ、「エラー: DEP0700 : アプリケーション登録に失敗しました。The Appx package's manifest is invalid.: Cannot register package .... because of a problem with Function element usage:0001 0006: Value is blocked, and not allowed on this bus (0x80073cf6)」なるエラーが発生(´・ω・`)

ん?、と思って調べてみたところ、これに引っかかっているのかしら。

https://msdn.microsoft.com/ja-jp/library/windows/apps/bg182882.aspx#limitations_of_the_hid_api


Usage Id : HID_USAGE_GENERIC_KEYBOARD(0x06)はブロックされているということで、KeaboardなHIDデバイスとして認識される温度センサーを扱えなかったのでした(´・ω・`)

2015-09-06

[]WPFでもDIしたいよね(・∀・)

わい氏、コンテナ無しではアプリケーションの構築もできないひ弱な現代っ子なわけですが(´・ω・`)

っというわけで、Webアプリなんかだけでなく、WPFアプリケーションでもコンテナを使ってアプリケーションをくみ上げる方法についておきます。

方式

WPFで依存性解決というと、SericeLocatorを作ってXAML上に定義してみたいな方法もありますが。

自分のやりかたはViewの生成もコンテナにやらせて、ViewへのViewModelのインジェクション以下、ApplicationModelやそれらが使用するサービスインターフェース(実装)なんかもコンテナに依存関係を自動的に解決させるやりかたです。

ソース

とりあえずソースは以下のExample01にあります。

https://github.com/usausa/Example-Net-WPF

色々と余計な点も含んでいるソースですが(´д`;)、毎度おなじみのNinjectを使用して依存性解決をしている例になります。

どんなサンプル?

依存性解決に関する部分はちょこっとだけなので、先にサンプルの内容を説明しておきます。

  • Example.WpfApplicationはMainWindowとOptionWindowの2つの画面を持ちます
  • (WPF以外でも共通的に使用されるという想定の)別プロジェクトExample.CoreにあるLongProcessWorkerを使用します
  • LongProcessWorkerは、なんか長時間かかる処理をバックグラウンドでやってくれるものであり、進捗と完了をイベントベースで使用者に通知します
  • MainWindowには、LongProcessWorkerの開始/キャンセル機能と、動作状況のログ表示機能があります
  • OptionWindowはLongProcessWorkerのパラメータを設定するツールウインドウです
  • OptionWindowはMainWindowのタイトルバーにあるボタンで表示の切り替えができます

つまらんサンプルですな(´・ω・`)

依存性解決の使用箇所

このサンプルでは、以下の箇所で依存性解決を使用しています

  • Windowへの各ViewModelの注入
  • ViewModelへのLongProcessWorker及び設定を表すOptionSettingsの注入
  • OptionWindowの表示を制御するためのIWindowManager実装のViewModelへの注入

WPFアプリケーションでNinjectを使う

っで、ここからが本題。

新規に作ったWPFアプリケーションでコンテナを使用するようにするステップはほんのわずかです。

ここではサンプルのソースを例に、コンテナを使用するための手順を書いていきます。

MainWindowの生成/表示をコンテナベースにする

まず、App.xamlでのStartupUriによる画面の表示をやめます。

サンプルでは以下のようにStartupUriを使用しなくなっています。

<Application x:Class="Example.WpfApplication.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             ShutdownMode="OnMainWindowClose">
...

なお、ShutdownModeをOnMainWindowCloseにしているのは、このサンプルではMainWindowの他にOptionWindowがあるためです。

コンテナの準備と画面の生成/表示

次に、StartupUriをなくしたかわりに、AppクラスのOnStartupをoverrideしてコンテナによるMainWindowの生成とその表示を行うわけですが、先にAppクラスの内容を載せてしまいましょうか(・ω・)

namespace Example.WpfApplication
{
    using System;
    using System.Windows;

    using Example.Models;
    using Example.WpfApplication.Infrastructure;
    using Example.WpfApplication.Models;
    using Example.WpfApplication.Views;

    using Ninject;

    public sealed partial class App : IDisposable
    {
        private readonly StandardKernel kernel = new StandardKernel();

        public void Dispose()
        {
            kernel.Dispose();
        }

        protected override void OnStartup(StartupEventArgs e)
        {
            RegisterComponents();

            MainWindow = kernel.Get<MainWindow>();
            MainWindow.Show();
        }

        private void RegisterComponents()
        {
            // Infrastructure
            kernel.Bind<IDependencyResolver>().To<NinjectDependencyResolver>().InSingletonScope();

            // Core domain model
            kernel.Bind<LongProcessWorker>().ToSelf().InSingletonScope(); // Singleton

            // Application model
            kernel.Bind<OptionSettings>().ToConstant(new OptionSettings(5, 10));    // Configured object

            // View & ViewModel
            kernel.Bind<OptionViewModel>().ToSelf();
            kernel.Bind<OptionWindow>().ToSelf();

            kernel.Bind<MainViewModel>().ToSelf();
            kernel.Bind<MainWindow>().ToSelf();

            // View service
            kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
        }
    }
}

っで、説明ですが、まずはコンテナの定義からはじめましょうか。


メンバ変数のStandardKernel kernelがNinjectのコンテナで、RegisterComponents()メソッドではコンテナに各種コンポーネント登録しています。

RegisterComponents()ですが、まず、わかりやすいところから行くと、ViewとVideModelのコンテナへの登録があります。

次に、LongProcessWorkerとOptionSettingsについてですが、これらはSingletonとして設定したり、(事前に設定済みの)固定のインスタンス登録することで、参照される複数のViewModelに同一のインスタンスを注入するようにしています。


最後に、RegisterComponents()の最初と最後のIDependencyResolver/NinjectDependencyResolver及びIWindowManager/WindowManagerについてですが、これは別ウインドウを表示管理するWindowManagerについて、その実装への依存とNinject自体への依存を抽象化するための仕掛けです。

ViewModelはIWindowManager経由で別Windowの表示を制御しますが、インターフェースと実装を分離することでその実装を知る必要はなくなっています。

また、WindowManager内ではコンテナ経由でWindowを生成しますが、そこをNijectに依存しないようにしているのがIDependencyResolverです。


ある程度規模の大きなアプリケーションであれば、コンテナの管理やRegisterComponents()を専用のBootstrapクラスなどに分離したりもしますが、ここではAppクラスで全てを行っています。

依存性を注入されるクラスの実装

っで、コンテナの登録については以上ですが、次は依存性を注入される各クラスについてです。

依存性の注入はNinjectのコンストラクタインジェクションで行っているので、ViewおよびViewModelクラスのコンストラクタは次のような感じになります。

public partial class MainWindow
{
    public MainWindow(MainViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}
public class MainViewModel : DisposableViewModelBase
{
...
    public MainViewModel(LongProcessWorker worker, OptionSettings optionSettings, IWindowManager windowManager)
    {
...

これで、MainWindowがコンテナで生成される際にはMainViewModelのインスタンスが作成され、それがコンストラクタ引数でわかってくることになります。

また、生成されるMainViewModelのコンストラクタには同様にコンテナから各種インスタンスがわたってきますが、これらはInSingletonScope()やToConstant()で登録しているものなので、単一のインスタンスが他画面などと共有されることになります。


なお、このサンプルではやっていませんが、同様にコンポーネント登録することで、View-ViewModel-ApplicationModel-ExternalInterfaceといった階層構造について、そのライフサイクル管理と依存性の注入をコンテナに任せることが可能になります。


楽ちんぽ(・∀・)

2015-08-30

[]Smart.Windows

こちら。

https://github.com/usausa/Net-Smart-Windows

なんぞ?

よくあるWPFアプリ用のサポートライブラリです(・ω・)

実用には自前のものを使うのではなく、メジャーどころを使えばよいと思いますが。

とりあえず学習教材用に整理したのでGitHubに置いておきます。

2015-06-05

[]ミニマムMessenger

MVVMなやつね。

これで良い(・ω・)?

インフラ

public class MessengerEventArgs : EventArgs
{
    public string Message { get; private set; }

    public object Parameter { get; private set; }

    public MessengerEventArgs(string message, object parameter)
    {
        Message = message;
        Parameter = parameter;
    }
}
public class Messenger
{
    public event EventHandler<MessengerEventArgs> Recieved;

    public void Send(string message = null, object parameter = null)
    {
        var handler = Recieved;
        if (handler != null)
        {
            handler(this, new MessengerEventArgs(message, parameter));
        }
    }
}
public class MessageTrigger : TriggerBase<FrameworkElement>
{
    public static readonly DependencyProperty MessengerProperty = DependencyProperty.Register("Messenger", typeof(Messenger), typeof(MessageTrigger), new PropertyMetadata(MessengerChanged));

    public Messenger Messenger
    {
        get { return (Messenger)GetValue(MessengerProperty); }
        set { SetValue(MessengerProperty, value); }
    }

    public string Message { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Unloaded += OnUnloaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Unloaded -= OnUnloaded;
        base.OnDetaching();
    }

    private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    {
        if (Messenger != null)
        {
            Messenger.Recieved -= MessengerOnRecieved;
        }
    }

    private static void MessengerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue == e.NewValue)
        {
            return;
        }

        var trigger = (MessageTrigger)obj;

        if ((e.OldValue != null) && (trigger.Messenger != null))
        {
            trigger.Messenger.Recieved -= trigger.MessengerOnRecieved;
        }

        if ((e.NewValue != null) && (trigger.Messenger != null))
        {
            trigger.Messenger.Recieved += trigger.MessengerOnRecieved;
        }
    }

    private void MessengerOnRecieved(object sender, MessengerEventArgs e)
    {
        if (String.IsNullOrEmpty(Message) || (Message == e.Message))
        {
            InvokeActions(e.Parameter);
        }
    }
}

使う側

VMからWindowを閉じる場合。

こういうTriggerActionを作って、

public class WindowCloseAction : TriggerAction<Window>
{
    protected override void Invoke(object parameter)
    {
        AssociatedObject.Close();
    }
}

ViewModelからはこんな風にMessengerを使用して、

public class ViewModel
{
    private Messenger messenger;

    public Messenger Messenger
    {
        get { return messenger ?? (messenger = new Messenger()); }
    }

...
    private void Close()
    {
        Messenger.Send("Close");
    }
}

XAMLはこんな感じで。

<i:Interaction.Triggers>
    <my:MessageTrigger Message="Close" Messenger="{Binding Messenger, Mode=OneWay}">
        <my:WindowCloseAction/>
    </my:MessageTrigger>
...
</i:Interaction.Triggers>

2015-04-08

[]最近のASP.NET MVCでのお仕事、の感想とか

あれからもう5年以上( ˘ω˘ )

最近もまた1本ASP.NET MVCでシステムを作ってみたので*1、ちょっと比較とかしてみようかと思って。

システム構成

今回の構成はこんな感じ(・ω・)

OSWindows Server 2012 R2
DBSQL Server 2014
WebASP.NET MVC 5
データ量1テナントあたり8000件/日追加

OSDBなんかは順調にバージョンアップ

フレームワークに関しては、MVCの他にWeb APIやSignalRなんかも使用してますが、詳細は次で。

使用ライブラリ

主要なものをABC順で。

AutoMapperオブジェクトマッピング
Dapperデータアクセス
Elmah例外処理
Glimpse診断
ASP.NET MVCMVC
ASP.NET SignalRリアルタイム処理
ASP.NET Web APIAPI
NinjectDependency Injection
Quartz簡易バックグラウンド処理用
StackExchange.RedisRedis用

どれも定番なので特に語ることはなし(・ω・)

個別の要素についてはまた後で。

ソリューション構成

今回、Webの他にWindows CE端末のクライアントや周辺ツールなんかもあるわけですが、主な構成としては以下のようなプロジェクトで構成。

Xxxxx.ApiAPI用のRequest/Response構造体等
Xxxxx.DomainEnum(ストラテジ)的なものや固有ロジック等、入れ物じゃないよ
Xxxxx.Infrastructure拡張メソッドが主*2
Xxxxx.Infrastructure.WebMVC、SignalR、API関連の拡張類
Xxxxx.Infrastructure.WindowsWPF関連のBehaviorやらConverterやら
Xxxxx.Report帳票ツール
Xxxxx.ClientCEクライアント
Xxxxx.Server.Coreサーバ側の要はM
Xxxxx.Server.Core.Tests上記テスト
Xxxxx.Server.Web.Admin管理Webサイト
Xxxxx.Server.Web.Tenant業務Webサイト
Database.Tenant業務データベースプロジェクト
Database.Master管理データベースプロジェクト

データベースプロジェクトが2つあるのは、このシステムはマルチテナントを扱うから。

マルチテナントを考慮したテーブル設計なんてしたくないし、っというわけ(?)で、管理用のデータベースと、テナント毎に物理的に分かれたの業務データベースから構成してますにゃん。

アーキテクチャというか諸要素雑感

っで、構成が出たところで、諸要素についての雑感をゆる〜く(・ω・)

データアクセス

データアクセスについては最近はDapper一択。

理由は性能と言うよりも、ORMで表現しきれないSQLなんて普通に出てくるから(・ω・)


まあ、Expressions使って無駄にランタイムでコストに見合わないアホな処理もしたくはないけど、単純クエリの生成ならエンティティクラスからT4で生成で良いじゃんとか。

正直、抽象化された実行プランそのものを直接組み立てるくらいのAPIにならないと、RDB用の抽象化された高度なフレームワークとかいらんかな〜、っという感じで。


あとそうそう、Dapperと言えばマルチマッピング大活躍(`・ω・´)

俺にはこういうのでいいんだよ、こういうので。

キャッシュ

ぎょーむアプリでもRedisとかは使う時代。

単純なキャッシュというよりも、ソート済みセットの使用だったり、pub/sub的な使い方だけど。

ルーティング

MVCについては変わったことはなく。

APIについては、カスタムIHttpControllerSelectorで"api/{version}/{controller}/{action}/{id}"を使用。

ちなみにAPIについてはCE端末専用の口で、Web用途では未使用、っというか、画面で必要なものはMVCのコントローラーでPartialView使ってフラグメントを返すとかにしちゃってますが。


あと「おまえが本当に必要だったものはリソースへのアクセスではなくRPCなのではないか?」問題、つまりはそれってRESTなの?、的な話についてもちょっと。

まあ、今回に関して言えば、(イベントとかの)仮想リソース的な扱いで問題ない範囲なので、そういう設計、っという感じで。


世間一般的な話をするならどうだろね(・ω・)?

まあ、欲しいのはRESTというよりRPCというケースが多いんだろうけど、抽象化された仮想リソース的な設計で済むケースも同じく多いんとちゃうん?、くらいのイメージかしらね?

Area

本プロジェクトでは未使用。

同時進行していた別プロジェクトでは使ったけど(・ω・)


Areaを使うかどうかは、要はサブモジュール(サブシステム)の管理をどうするかという話で。

それなりの規模、っというかバラエティに富んだ機能を持たない限りは必要無いし。

最初、なにも考えずに管理機能をAreaで、…とか思っちゃったけど、今回のはマルチテナント自体の管理だし、意味合いが違うな、っということで、別Webアプリで構成。

認証

ASP.NET Identityとか使ってないよ。

これどうなるかわからんし(´д`;)


…っといいつつも、Identityモドキのような何かを自前してしまっているというしょうもないオチ(・∀・;)


まあ「認証APIとパッケージ管理ツールで納得いくものに出会うのは難しい」っという私の言葉を残しておきます。

リアルタイムWeb

SignalR使ってますが、世間にある例ってチャットとかつまんねーものしかないけど、ぎょーむアプリではリアルタイムな処理って何に使っているの、っというあたりについて。

本システムでの用途は以下のあたり。

  • ダッシュボードの更新
  • 簡易メッセージング
  • レポートのキュー

このシステムでは、Webにはダッシュボードがあって、主に開いているのもその画面な使い方で(・ω・)

その情報は、端末からのリクエストや、期限のあるアラートなんかもあるので定期的な更新なんかも発生。

っで、その通知にSignalRを使用。

なお、サーバトリガーの通知はIHubConnectionContext<dynamic>を使うけど、IHubConnectionContext<dynamic>をそのまま使うのではなく、通知用のインターフェース及び実装を用意して、実装にIHubConnectionContext<dynamic>をDIする形でラッピングして使用。


ちなみに、アラートトリガーの発行には印プロセスでQuartzを使っていますが。

これ、本当は別プロセスに分離して、そこからSignalRを使って通知したいところですが、現状、SignalRのホストなしに通知だけを行うって出来ないのよね。*3

将来的にはサポートされる(?)ようですが、今回はこんなんで(・ω・)


簡易メッセージングは、メールモドキというか、ポケベルみたいな機能だけど、これはよくある例で。

自分宛のメッセージが届いたらヘッダ部に未読件数を表示とか、そういう用途に使用。

キューの処理は、印刷用のツールがいるんだけど、そこへ速やかに印刷要求イベントを通知するために使用。

Dependency Injection

Ninjectを使用。

特にこだわりはなく、なんとなくNinjectを使っていますが(・ω・)


コンテナ自体はNinjectを使うとして、ASP.NET MVCだとアダプタが、

  • System.Web.Mvc.IDependencyResolver(MVC用)
  • System.Web.Http.Dependencies.IDependencyResolver(Web API用)
  • Microsoft.AspNet.SignalR.IDependencyResolver(SignalR用)

全然違うじゃん!、っという笑い話がありますが。*4

幻滅しました…、ASP.NET MVCのファンやめます、もとい、これも6では解消されるらしいので期待、っと(・ω・)

JSON

Json.NETのみを使用。

例えば速度面とかで言えば、Json.NETよりも優秀なシリアライザもありますが、そっちはカスタマイズポイントが不十分だったりもするのでJson.NETのみの使用で統一。

日付をISO 8601にしたり、クライアント側はCamelケース、サーバ側はPascalケースで統一するだの小細工したいときには、JsonConverterだったりIContractResolverみたいなカスタマイズポイントが用意されているJson.NETステキ、っということで(・∀・;)

オブジェクトマッピング

自動マッピングとかも賛否はあるけどね(・ω・)

エンティティと、APIのRequest/Responseと、WebのViewModel、っという呼び方はしていなくて、Formに対応するものをXxxFormってしているんだけど、そのマッピングにはAutoMapperを使用。

なお、あくまで使用される領域が異なる入れ物とエンティティ(入れ物はその射影みたいな)の変換用であって、構造的な変換とかを対象外。

なので、マップの定義もForMemberはIgnoreがあるくらいで、色々ルールを書かなくてはならないようのは違う、っという方針

ユニットテスト

Xxxxx.Server.Coreのユニットテストしか用意していないわけですが(・∀・;)

なにをテストしたかったんだと思う?、っという点を考えたときに。

テスト(主にCIで回すリグレッションテスト)する価値が高い部分から手をつけるわけで、そう考えるとコントローラーの処理なんて自明だし、ビューのテストはコストに見合わない、っと。


っで、テストの対象は効果の高い処理(ユースケースに対応する、UIの絡まない)部分に絞った上で、そこのテストをどう書くのか?、っという点について。

要はモックをどうするか、っというあたりの話ですけど(・ω・)


処理がRDBの制約に依存するものであれば、そこは本物のDBを使ったテストをしたいところだけど、そこの価値を更に考えた上で、またユニットテストなのかという点も考えて。

ユニットテストの対象としては、あくまで「処理」の振る舞いに絞り、モックはIDbConnectionのレイヤで用意して、他は具象クラスを使う形で記述。


テストの為だけにIFと実装を分離するとか変な話ではなく、本来IFと実装をわけて設計すべき箇所にモックを差し込めればそれで十分、っというのが本来あるべき形とちゃうん(・ω・)?、みたいな感じで。

また、その場合は明示的なモック実装があった方が楽にテストを書けたりもするので、黒魔術的なモックの生成は出番が無いという結果に。

この辺の考えは、このプロジェクトに限らず最近の自分の考えだけど。

まとめ

ってな感じでやってみたわけですが、どんなもんでしょう(・ω・)?


本当は、もっとJSときゃっきゃうふふしたり出来ると、モダンなWebアプリっぽくなると思いますがこのロリコンめ。

ぎょーむ用なので尖ってはいないし、むしろ手堅さが見える作りなのです٩(๑•ㅅ•๑)و

*1:同時にもう2本ASP.NET MVC案件が走っていたりもした

*2:.NETだと共通ライブラリとして用意するのは拡張メソッド群だけでええんちゃう?感はあるよね

*3:そのためにセルフホストするのもチト違う

*4:後はQuartz用にもしかけを作っていたりして