Task で非同期処理をつくる(.NET Framework 4.0用) (゜A゜)

第3回 TaskクラスとPLINQ(Parallel LINQ) − @IT を参考に非同期処理を実装してみる。

なお、.NET Framework 4.5 、C#5.0から提供される async, await での実装はしていません。
.NET Framework 4.0 での実装時の参考にしてください。

環境

目的

Webアプリで時間のかかる処理を実行しつつ、Responseは返して画面描画は完了させる。

並行処理

参考にした記事では、task.Wait() を処理待ちをしていたけどWebアプリ想定として処理待ちはしません。また、WebのResponse送信完了後も処理が継続していることを確認するためにnotify()を完了後に実行するよう、task.ContinueWith()で指定してます。

public class TaskService
{
    private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    void notify(Task t)
    {
        logger.Info("ヽ(*゚д゚)ノカイバー {0} done", t.Id);
    }

    public void Execute()
    {
        var task = Task.Factory.StartNew(() =>
        {
           // 長い処理
            for (int i = 0; i < 10000; i++) logger.Info("ヽ(*゚д゚)ノカイバー");
        }).ContinueWith(notify);

        for (int i = 0; i < 100; i++) logger.Info('A');
    }
}

クラスは以下の通り。ASP.NET MVC のコントローラです。

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Message = "ヽ(*゚д゚)ノカイバー";
        var service = new TaskService();
        service.Execute();
        TaskService.Execute();
        return View();
    }
}

出力したログの結果は以下の通り。

2012-09-21 01:05:06.4620 INFO A
 ~中略~
2012-09-21 01:06:56.8451 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.8591 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.8722 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.8862 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.8862 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.9142 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.9292 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.9442 INFO ヽ(*゚д゚)ノカイバー
2012-09-21 01:06:56.9582 INFO ヽ(*゚д゚)ノカイバー 1 done

TFS2012自動ビルドでデプロイすると「'managedRuntimeVersion' プロパティが 'v4.0' に設定されています。このアプリケーションには 'v4.5' が必要です。」への対応方法 #tfsug

IIS8.0に.NET Framework 4.5 のアプリケーションをデプロイしようとしたらうまくいかなかったのでメモ。

環境

現象

TFS2012からの自動ビルドでIIS8.0に ASP.NET MVC 4アプリケーションをデプロイしようとしたら以下のエラーが表示されます。
自動ビルドに設定した内容は過去エントリ(TFSの自動ビルドでWebアプリのデプロイ、配布パッケージの作成を同時に行う #tfsug - kaji_3's blog)参照。

C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets (3847): Web 配置タスクに失敗しました。(使用しようとしているアプリケーション プールでは、'managedRuntimeVersion' プロパティが 'v4.0' に設定されています。このアプリケーションには 'v4.5' が必要です。)

解析

ただし、Visual Studio 2012 の発行ウィザードでデプロイすると成功し、TOP画面が表示されます。また、Web Deploy用発行パッケージを作成してバッチでデプロイした場合も成功します。

TFSからの自動ビルド時のデプロイにはMSBuildの引数オプションの「/p:DeployOnBuild=True」を利用しているため、MSBuild固有の問題ではないかと推測します。

対応

色々検索して、VisualStudio2010から2012にアップグレードしたらデプロイできなくなった方がいました。
MSBuild deploy failing after upgrade to .NET 4.5 - Stack Overflow
そこで紹介されているエントリを参考にします。
Sayed Ibrahim Hashimi - MSBuild, C#, Visual Studio, Training, and more - Visual Studio project compatability and VisualStudioVersion

どうやら、Visual Studio 2010 をインストールしている環境で Visual Studio 2012 をインストールすると、MSBuildの targetの定義ファイルがVS2010の方を参照してしまうようです。
なので、VS2012を参照するよう以下のオプションを

ビルド定義→プロセス→ビルドプロセスパラメータ→MSBuild引数に、

/p:VisualStudioVersion=11.0

を追加します。これでオッケー!!

TFSの自動ビルドでWebアプリのデプロイ、配布パッケージの作成を同時に行う #tfsug

TFSを利用していつも継続的インテグレーションをしてます。

ビルドが成功したら動作確認用サーバに上げたくなります。
そして検証が完了したらデプロイしたアプリケーションを受入テスト等の別環境にデプロイしたくなります。

そんな課題を MSBuild のオプションを指定するだけで楽に解決する事ができます。

環境

設定内容

対象とする自動ビルドのビルド定義を開き「プロセス」→「ビルドプロセスパラメータ」→「MSBuild引数」に以下を指定します。/p:MSDeployServiceUrl、/p:DeployIISAppPath の値は任意に変更してください。

/p:CreatePackageOnPublish=true /p:DeployOnBuild=True /p:DeployTarget=MsDeployPublish /p:MSDeployPublishMethod=InProc /p:MSDeployServiceUrl=localhost /p:DeployIISAppPath="Default Web Site/TargetApp" 

(ここです)
f:id:kaji_3:20120915003524p:plain

配布パッケージは、ビルド出力の格納先フォルダにあります。
(格納先フォルダの場所は、「ビルドの規定値」→「ステージング場所」です。)

格納先フォルダの実行したビルド結果のフォルダ内、「_PublishedWebsites」フォルダの下にあります。

当然ですが、デプロイについてはIISにデプロイする権限があるユーザで実行する必要があります。ご注意ください。

Livet ViewModel のValidation で ValidationGroup を作成する

LivetというかWPFでの単項目のチェックの実装について悩んでいた時、Nine Works: WPFでのデータ検証 1:IDataErrorInfoを使ったデータ検証 というエントリを見つけたので参考にさせて頂きました。
しかし、実案件に導入した際、ASP.NET の ValidationGroup のようにValidation対象をグループ化したいという要望が出てきました。しかし、DataAnotation にはValidationGroup に該当するものを見つける事ができませんでした。そのため、DiplayAttribute に存在する GroupName を ValidationGroup として利用する機能を追加します。

環境

補足

  • DisplayAttribute のメンバである、Groupnameを利用しますが、このプロパティの説明「UI でのフィールドのグループ化に使用される値を取得または設定します。」に則った利用法なのか調べたかわかりませんでした。利用時は、十分検証してくださいい。

追加した機能

  • プロパティをグループ化し、検証時指定グループの検証結果だけ取得する。エラーメッセージも同様。
  • 1つのプロパティに複数グループの指定もカンマ区切りで指定可能。
  • isValidメソッドを利用した検証時、Errorプロパティに検証結果のメッセージが設定される

検証を行うViewModelクラス

// using 省略

namespace LivetWPFApplication.ViewModels
{

    /// <summary>
    /// DataAnnotationによるプロパティの検証を可能とするViewModelです。
    /// </summary>
    public class ValidationViewModel : ViewModel, IDataErrorInfo
    {
        protected Dictionary<string, string> _errors = new Dictionary<string, string>();

        /// <summary>
        /// オブジェクトに関する間違いを示すエラー メッセージを取得します。
        /// </summary>
        public string Error
        {
            get
            {
                return GetErrorMessage(String.Empty);
            }
        }

        /// <summary>
        /// DataAnnotationsを使ってデータ検証を行い
        /// 指定した名前のプロパティに関するエラー メッセージを取得します。
        /// </summary>
        /// <param name="propName"></param>
        /// <returns></returns>
        public string this[string propName]
        {
            get
            {
                var result = new List<ValidationResult>();
                if (Validator.TryValidateProperty(
                        GetType().GetProperty(propName).GetValue(this, null),
                        new ValidationContext(this, null, null) { MemberName = propName },
                        result))
                {
                    // エラー無し
                    _errors[propName] = null;
                }
                else
                {
                    _errors[propName] = result.First().ErrorMessage;
                }

                return _errors[propName];
            }
        }

        /// <summary>
        /// 指定したグループに属するプロパティの値が妥当かを返します。
        /// </summary>
        /// <param name="validationGroupName">検証を行うグループ</param>
        /// <returns></returns>
        public bool isValid(string validationGroupName)
        {
            var errorMessages = GetError(validationGroupName);
            // エラーメッセージの更新通知
            RaiseErrorChanged();
            return errorMessages.Count() == 0;
        }

        /// <summary>
        ///  指定したグループに属するプロパティのエラーメッセージのサマリを取得します。
        /// </summary>
        /// <param name="validationGroupName">検証を行うグループ</param>
        /// <returns></returns>
        public string GetErrorMessage(string validationGroupName)
        {
            var errorMessages = GetError(validationGroupName);
            return String.Join(System.Environment.NewLine, errorMessages.Values);
        }

        /// <summary>
        /// オブジェクトのすべてのプロパティの値が妥当かを返します。
        /// </summary>
        /// <returns></returns>
        public bool isValid()
        {
            return isValid(String.Empty);
        }

        // Errorの変更通知
        protected virtual void RaiseErrorChanged()
        {
            RaisePropertyChanged("Error");
        }


        /// <summary>
        /// 指定したグループのエラーを取得します。
        /// </summary>
        /// <param name="validationGroupName"></param>
        /// <returns>エラーのプロパティ名、エラーメッセージ</returns>
        private Dictionary<string, string> GetError(string validationGroupName)
        {
            IEnumerable<KeyValuePair<string, string>> errorMessages;

            if (String.IsNullOrEmpty(validationGroupName))
            {
                errorMessages = _errors.Where(x => !String.IsNullOrEmpty(x.Value));
            }
            else
            {
                errorMessages = _errors.Where(x =>
                        IsContaindValidationGroup(x.Key, validationGroupName) && _errors[x.Key] != null);
            }

            return errorMessages.ToDictionary(x => x.Key, x => x.Value);

        }

        /// <summary>
        /// 対象プロパティが、指定したグループに属するかの結果を返します。
        /// </summary>
        /// <param name="propertyName">対象プロパティ名</param>
        /// <param name="validationGroupName">指定を行うグループ</param>
        /// <returns></returns>
        private bool IsContaindValidationGroup(string propertyName, string validationGroupName)
        {
            var attributes =
                    Attribute.GetCustomAttributes(GetType().GetProperty(propertyName), typeof(DisplayAttribute));

            if (attributes.Count() == 0)
            {
                return false;
            }
            var displayAttr = attributes[0] as DisplayAttribute;

            return displayAttr.GetGroupName().Split(',').ToList().Exists(x => x.Trim() == validationGroupName);
        }

    }

}

Validationを行うViewModelの実装例

public class TargetVM : ValidationViewModel
{

    #region Value1変更通知プロパティ
    private string _Value1;

    [Display(Name = "値1", GroupName = "grp1")]
    [Required]
    public string Value1
    {
        get
        { return _Value1; }
        set
        {
            if (_Value1 == value)
                return;
            _Value1 = value;
            RaisePropertyChanged("Value1");
        }
    }
    #endregion


    #region Value2変更通知プロパティ
    private int? _Value2;

    [Required]
    [Display(Name = "値2", GroupName = "grp2")]
    public int? Value2
    {
        get
        { return _Value2; }
        set
        {
            if (_Value2 == value)
                return;
            _Value2 = value;
            RaisePropertyChanged("Value2");
        }
    }
    #endregion


    #region Value3変更通知プロパティ
    private string _Value3;

    [Required]
    [Display(GroupName="grp3,grp4")]
    public string Value3
    {
        get
        { return _Value3; }
        set
        { 
            if (_Value3 == value)
                return;
            _Value3 = value;
            RaisePropertyChanged("Value3");
        }
    }
    #endregion


    public bool Click()
    {
        return isValid("grp1");
    }

    public bool ClickGroup2()
    {
        return isValid("grp2");
    }

    public bool ClickGroup3()
    {
        return isValid("grp3");
    }

    public bool ClickGroup4()
    {
        return isValid("grp4");
    }

    public bool ClickGroupAll()
    {
        return isValid();
    }
}

Oracle DataPump Export の expdp オプションでFLASH_BACK_TIMEを設定する

パラメータファイル経由ではなく、コマンドオプションで利用した場合、文字列のエスケープで苦労したのでメモ。

環境

Oracle 11g Database

expdp kaji/scott SCHEMAS=kaji DIRECTORY=kaji_DIR1 DUMPFILE=kaji_DATA.dmp LOGFILE=kaji_DATA.log FLASHBACK_TIME=\"TO_TIMESTAMP('2012/09/12 15:55:00', 'YYYY/MM/DD HH24:MI:SS')\"

Appcmd を利用してWebアプリケーションをIISに作成する

IISの設定は。。もう手でやりたくないです(しろめ

Microsoft Windows Server 2008リソースキット IIS 7.0編 (マイクロソフト公式解説書)

Microsoft Windows Server 2008リソースキット IIS 7.0編 (マイクロソフト公式解説書)

IISのパフォーマンスが大幅劣化した際、参考にしたこの本ですがその中に Appcmd なるIISの設定をコマンドで出来る方法があるじゃないですか。

という事で Webアプリケーションを作成して、MIMEの種類を追加するバッチを作ってみました。

環境

  • IIS 8
  • Windows 8

バッチ

rem 管理者として実行すること
cd %windir%\system32\inetsrv

rem 存在していた場合、アプリケーションを削除して作成
appcmd delete app "Default Web Site/clickOnePub
appcmd add app /site.name:"Default Web Site" /path:/clickOnePub

rem 変更前dump
appcmd list config "Default Web Site/clickOnePub" /section:staticContent > beforeConfig.txt

rem 拡張子を登録
appcmd set config "Default Web Site/clickOnePub" /section:staticContent /+"[fileExtension='test',mimeType='application/test']" 

【参考】
Appcmd.exe (IIS 8)
http://technet.microsoft.com/ja-jp/library/cc725608(v=WS.10).aspx

TFS 自動ビルドで Web 配置パッケージを作成するための設定

ビルド定義を編集する

自動ビルドで実行される MSBuild の引数を指定します。

チームエクスプローラーからビルド定義の編集を行い、MSBuild引数に以下を追加します。

/p:CreatePackageOnPublish=true

こんな感じ。


f:id:kaji_3:20120821094852j:plain

ビルド実行後、ビルド結果の出力先に保存されてます。

本当は検証用サーバに発行もしたい。オプションのあたりはついているのだけど検証したら!

【参考】 Create web deploy package during a tfs 2010 build