いぇいいぇい

ASP.NET、PHPを中心に書いています

(LINQ)SkipWhileの使い方、間違えていませんか?

はい!僕、間違えていました!

SkipWhile、単純に「条件に一致する要素をスキップする」ものだと思っていました。まったくの勘違い。アホですねー。

正しくは「条件に一致する限り要素をスキップする」ということみたいです。

以下サンプルです。

void Main()
{
    var names = new string[] { "太郎", "花子", "太郎", "花子" };
    var namesExclude = new string[] { "太郎" };

    // namesから"太郎"を除外したいとします。
    // まずはSkipWhileの場合
    var nameFiltered1 = names.SkipWhile(s => s == "太郎");
    Console.WriteLine("SkipWhile:");
    foreach (var name in nameFiltered1) {
        Console.WriteLine(name);
    }

    // Whereの場合は全要素フィルタリングできます
    var nameFiltered2 = names.Where(s => s != "太郎");
    Console.WriteLine("Where:");
    foreach (var name in nameFiltered2) {
        Console.WriteLine(name);
    }
}

出力結果

SkipWhile:
花子
太郎
花子
Where:
花子
花子

多分勘違いしたのはWhileの逆バージョンがほしくて、SkipWhileを見た時に「ああ、あるじゃん、これこれ」みたいな感じで勝手に自分の都合よく解釈しちゃったんだろうと思います。怖い怖い。

Realtek HD Audioの音飛び問題が解決

去年にPCをアップグレードしたときから悩まされてきた問題が解決しました。非常にうれしい。

現象

現象としては数分から数十分の頻度で音飛びが発生。具体的には突然音が「プツッ」っと止まって、1秒くらいでまた音が再開するというもの。

原因は音飛びじゃなかった

最初は音飛び問題ということでエフェクトとか、コーデックが悪さをしているんじゃないかと思っていろいろ探していたのですが原因わからず一旦諦めていました。

先日OSをWindows10にアップグレードしてからも同じ現象が発生して落ち込んでいました。ところが現象発生時に「デバイスが切断されました」、「デバイスが接続されました」的なバルーン通知が出るではないですか。

これは音飛びじゃなくてデバイスの切断が発生する問題じゃないかと思い再度ネット検索したところ、以下のページを見つけました。Realtek HD Audioにジャックなどのコネクタの切断/接続が発生する問題があることがわかりました。

www.sevenforums.com

www.youtube.com

解決

リンク動画を参考にオーディオマネージャという常駐プログラムを停止し、exeもリネームしました。今のところ問題は発生していません。よかった。

具体的な手順は以下です。

  1. タスクマネージャからRAVCpl64.exeを停止
  2. "C:\Program Files\Realtek\Audio\HDA"のRAVCpl64.exeをRAVCpl64_renamed.exeなどにリネーム

(RtkNGUI64.exeも同様にリネームしたほうがいいかもしれません)

参考までに環境は以下です。

  • Windows 10(64bit)
  • Realtek High Definition Audio(driver 6.0.1.7535)
  • H97-PRO

(2016/02/10 追記)

Windowsの自動更新などでドライバが再インストールされた場合は、exeが復活してしまうようです。その際は、再度リネームすればOKです。

ASP.NET MVC + Entity framework Code Firstのプロジェクト構成を紹介します

自分の今のプロジェクトで採用しているプロジェクト構成を紹介してみようと思います。何が正解とかはないと思いますが1つの例として参考になるかと。こういう情報が少ないので他の人はどうやってるのかすごい興味があります。オープンソースのものをたまに見ることはあるけどブログとかでってのはあまり見る機会ないですからね。変なとこありましたらツッコミなどよろしくお願いします!

前提

自分の場合はWebアプリだけでなくバックグラウンド処理サービスがあり、複数プロセスからDBアクセスしたいという要件がありました。

そういう要件がなくて単純なWebアプリの場合は1つのプロジェクトだけでもいいと思います。

その場合はフォルダ(名前空間)として分類することになります。ただフォルダだけだと本当にクラス同士が依存していないかコンパイラチェックではわかりません。なので依存関係がないことを確実にチェックしたい人は別プロジェクトにするといいですよ。

プロジェクト分けは、各プロセスが何に依存するかということ基準に決めます。

概要

f:id:sumi2:20150630173156p:plain

VSで依存関係マップを生成してみました。7つのプロジェクトがありますね。この他にSiteToolというプロジェクトがありますが別slnになっているので見えません。矢印が参照を表すようですね。

プロジェクト名 種別 説明
AppName.Core ライブラリ アプリ共通コード
AppName.Models ライブラリ POCOモデルクラス
AppName.DAL ライブラリ データアクセス関連コード(DB/Blob/File/Redis)
AppName.Web Web Webアプリ
AppName.Jobs ライブラリ バックグラウンド処理のinterface定義
AppName.Sites ライブラリ バックグラウンド処理実装
AppName.Hangfire コンソール バックグラウンド処理用のWindowsサービス
AppName.SiteTool WinForms バックグラウンド処理の開発用GUIツール

では順に説明していきます。

AppName.Core

Coreにはアプリ共通のクラスを格納します。余計な依存関係はありません。クリーンです。Jsonくらいあってもいいかも。僕のプロジェクトにはアプリ例外クラス、エラーコード定義、ユーティリティ拡張メソッドなどがあります。小さいです。

もしアプリ共通で使うけど依存DLLが必要な場合は、こう考えます。

  1. 将来も含めてすべてのプロセスで必要になるなら追加する。
  2. そうでないなら別プロジェクトにする。

依存関係:

なし

AppName.Models

ModelsにはPOCOのモデルクラス「だけ」を格納します。DB関係ないモデルクラスも入ってもいいですが、こちらも余計な依存関係は持ち込まないようにします。

依存関係:

AppName.Core

AppName.DAL

DALにはデータアクセス関連のコードを格納します。ApplicationDbContext、DbMigrationsConfiguration、Redis、BlobStorageなどですね。接続文字列などは各プロセスプロジェクトのWeb.config(App.config)で定義したものを参照します。

依存関係:

AppName.Core
AppName.Models

AppName.Web

Webアプリ本体ですね。フロント側の処理になります。ポイントはAppName.Hangfireに依存していないことです。バックグラウンド処理のジョブを実行する場合はAppName.Jobsで定義したinterfaceでHangfireジョブをキックします。

依存関係:

AppName.Core
AppName.Models
AppName.DAL
AppName.Jobs

AppName.Jobs

バックグラウンドジョブのinterface定義のみを含みます。AppName.Webから依存関係を取り除くために必要になります。Hangfireのジョブクラスをinterfaceにすることで依存関係を取り除くことができます。

依存関係:

なし

AppName.Sites

バックグラウンド処理をここで実装します。Blobを事前にローカルにDLしたりして、今のところDALには依存せずに済んでいますね。

依存関係:

AppName.Core
AppName.Models

AppName.Hangfire

バックグラウンド処理用のWindowsサービスです。デバッグ用にコンソールでも動作するようになっています。運用時にはこいつを複数個起動します。モジュール更新時はプロセス停止が必要なので複数個必要です。

依存関係:

AppName.Core
AppName.Models
AppName.DAL
AppName.Jobs
AppName.Sites

AppName.SiteTool

バックグラウンド処理(AppName.Sites)を簡単に呼び出すために作った開発用のGUIツールです。Hangfire経由ではなくボタンクリックでジョブを実行できます。

依存関係:

AppName.Core
AppName.Models
AppName.Sites

まとめ

いかがでしたでしょうか。後半疲れて尻すぼみになりました。。

ぜひみなさんのプロジェクト構成も晒してみてください!

よく使うRazor記述パターン

自分用メモ。随時更新。

方針

Razor構文はいろいろな書き方をサポートしているけど、コードの見やすさを重視して、使うパターンをなるべく少なくする方針です。これだけ覚えとけばOKというものを書きます。

基本編

変数や式の結果を出力

基本ですね。@()を使うと変数や式の結果を出力できます。出力は自動でHtmlエスケープされます。

<span>@(Model.Title)</span>
<span>@(TimeZoneInfo.ConvertTimeFromUtc(Model.Time, Model.UserTimeZone).ToString("yyyy-MM-dd HH:mm"))</span>
<div class="exec_result_@(Model.Status == ExecStatus.Succeeded ? "suc" : "err")"></div>

Htmlエスケープしたくない場合は@Html.Raw()を使います。

<head>
@Html.Raw(Model.CommonCSSLink)
..
</head>

foreach文を使う

@foreach (var item in Model.Shops)
{
    ...
}

if文を使う

@if (User.IsInRole("Admin"))
{
    ...
}
else if (User.IsInRole("Operator"))
{
    ...
}
else
{
    ...
}

ヘルパー関数を呼ぶ

次項を参照。

アンカー(リンク)を出力

Url.Actionで指定アクションのURLを出力したり、Html.ActionLinkで指定アクションのaタグを出力したりできます。 アクション以外にController名、Area名、URLパラメータなども指定可能です。

<a href="@Url.Action("Logout")">ログアウト</a>
@Html.ActionLink("ログアウト", "Logout")

応用編

既定の名前空間を宣言する

よく使う名前空間があって、毎回cshtmlusingするのが面倒くさいときはViews\Web.configに名前空間を定義すればOKです。

  <system.web.webPages.razor>
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        ..
        <add namespace="Mamemaki.TestApp.Models" />
        <add namespace="Mamemaki.TestApp.ViewModels" />
        <add namespace="Mamemaki.TestApp.Helpers" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

URL、HTMLまたはAjax関連のヘルパー関数を拡張する

組み込み(WebViewPageクラスを参照)で用意されているヘルパーは以下の3つ。

  • UrlHelper
  • HtmlHelper
  • AjaxHelper

これらに関連する処理なら、以下のように拡張関数を定義してやればいいです。

/// <summary>
/// 現在のコントローラー名を小文字で返します。
/// </summary>
/// <param name="urlHelper"></param>
/// <returns></returns>
public static string CurrentController(this UrlHelper urlHelper)
{
    return urlHelper.RequestContext.RouteData.Values["controller"].ToString().Trim().ToLower();
}

/// <summary>
/// 店舗写真のURLを返します。
/// </summary>
/// <param name="urlHelper"></param>
/// <param name="shop"></param>
/// <returns></returns>
public static string ShopImageThumb(this UrlHelper urlHelper, ShopInfo shop)
{
    var routeValues = new RouteValueDictionary();
    routeValues.Add("id", shop.ID);
    if (shop.LastModifiedDate.HasValue)
        routeValues.Add("time", shop.LastModifiedDate.Value.Ticks);
    return urlHelper.Action("ImageThumb", "Shops", routeValues);
}

独自のヘルパーを定義する

独自のヘルパーを定義することも可能です。自分はそのような場面に出くわして無いので省略。

ほとんどの場面では組み込みのヘルパーを拡張すればよいと思います。だってビューの処理でHTMLに関係ない処理なんて思いつかないもんね。

ASP.NET Identityでユーザーに紐付いた別テーブルの情報を、DBアクセスなしにページヘッダに表示する方法

契約情報があって、その下に複数のユーザーをぶら下げるという構成の場合。(契約情報=ユーザーとしてもいいんだろうけど、担当者の変更などを考えると複数ユーザーにできたほうがよいと思った。)

ユーザーテーブルに契約情報テーブルを関連付ける

まずASP.NET Identityでユーザーテーブルに契約情報テーブルを関連付けたい。

調べた結果ApplicationUserは普通のモデルクラスなのでCode Firstなら単純に関連テーブルとして定義してやればいい。

public enum SubscriptionKind
{
    Free = 0,
    Standard = 1,
}

public enum SubscriptionStatus
{
    Active = 0,
    Cancelled = 1,
}

public class Subscription
{
    public int ID { get; set; }

    public SubscriptionKind SubscriptionKind { get; set; }

    public SubscriptionStatus SubscriptionStatus { get; set; }
}

public class ApplicationUser : IdentityUser<int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public int SubscriptionId { get; set; }

    public virtual Subscription Subscription { get; set; }

    public DateTime CreatedDate { get; set; }
}

契約情報をクレームに追加する

さらに契約情報をページヘッダに表示したい。毎回DBアクセスしたくないのでクレーム用Cookieから読み込みたい。

public class ApplicationClaimTypes
{
    public const string SubscriptionKind = "http://mamemaki.com/claims/subscriptionkind";
    public const string SubscriptionStatus = "http://mamemaki.com/claims/subscriptionstatus";
}

public class ApplicationUser : IdentityUser<int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
    {
        // authenticationType が CookieAuthenticationOptions.AuthenticationType で定義されているものと一致している必要があります
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // ここにカスタム ユーザー クレームを追加します
        userIdentity.AddClaim(new Claim(ApplicationClaimTypes.SubscriptionKind, this.Subscription.SubscriptionKind.ToString()));
        userIdentity.AddClaim(new Claim(ApplicationClaimTypes.SubscriptionStatus, this.Subscription.SubscriptionStatus.ToString()));
        return userIdentity;
    }
}

GenerateUserIdentityAsync()でクレームを追加してやるといいらしい。他のタイミングじゃダメなんだろうか。。?

契約情報をページヘッダに表示する

これで_Layout.cshtmlなどからDBアクセスせずに契約情報を表示できた。

<li>契約種別: @Html.Raw(((System.Security.Claims.ClaimsIdentity)User.Identity).FindFirst(ApplicationClaimTypes.SubscriptionKind).Value)</li>

こりゃ便利ですな。