none
MVC C#での特定のディレクトリ配下のディレクトリ名とディレクトリサイズの取得に関して RRS feed

  • 質問

  • どうも。こんばんは。

    タイトル通り、MVC C#にて特定のディレクトリ配下にあるディレクトリ名とそのディレクトリサイズを取得しようと試みています。

    調べながらmodelに下記のコードはかけました。

    public IEnumerable<DirectoryInfo> DirList = Directory.EnumerateDirectories(@"C:\tmp""*"SearchOption.TopDirectoryOnly).Select(x => newDirectoryInfo(x));

    しかしこれをビューのforeachで回して値の抽出を試みているのですがnameはあるのですがSizeとかlengthとかがなくて困っています。

    @foreach (var Dir inthis.Model.DirList) { <tr><td>@Dir.Name</td>//ディレクトリ名

    <td>@Dir.</td>//ディレクトリサイズ、にしたい。

    </tr> }

    どなたか解決策が分かるようでしたらご教授の程、お願いします。

     


    • 編集済み yamadamay 2016年5月16日 7:58
    2016年5月16日 7:57

回答

  • Model / View / Controller の分け方については、先のレスで書いたとおりですが、以下のようにするのが良いと思います。

    (1) Model では Controller から View に渡す情報の入れ物として Name, Size プロパティを待つ DirectoryNameSize クラス(先のレスのコードを見てください)と、ファイルシステムから情報を取得し List<DirectoryNameSize> 型のオブジェクトを作って返す GetDirectoryInfo メソッドを定義。

    (2) Controller では Model に定義した GetDirectoryInfo メソッドを呼び出し、戻り値の List<DirectoryNameSize> 型のオブジェクトを View に渡す。

    (3) View は Controller から渡された List<DirectoryNameSize> 型のオブジェクトから DirectoryNameSize クラスに定義されている Name, Size プロパティを使って情報を取り出して表示。

    Model をきちんと定義して基本に従って作れば (2), (3) のコードはかなりの部分を Visual Studio のウィザードを使って自動生成することができます。特に (3) は自力で一行もコードは書いてないです。それを、上記の作り方に反して、View の中でファイルシステムにアクセスして情報を取得しようとするとそうは行きません。

    もう一つ、ウィザードを利用できて作り方が簡単になる以上に多分重要なこととしてユニットテストがあると思います。View の中にデータソースにアクセスするようなコードを入れたらユニットテストはどうするのでしょう?

    ちなみに、Tak1wa さんが提案されたコードを Model に移して先のレスの Model コードを書き換えると以下のようになります。Model 以外は変更する必要はありません。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.IO;
    
    namespace Mvc4App.Models
    {
        public class DirectoryNameSize
        {
            public string Name { get; set; }
            public long Size { get; set; }
    
            public List<DirectoryNameSize> GetDirectoryInfo()
            {
                List<DirectoryNameSize> nameSize = new List<DirectoryNameSize>();
                
                // ディレクトリのパス(ワーカープロセスにアクセス権がある Views フォルダにしてみた)
                string path = @"C:\Users\PapikoPC\Documents\Visual Studio 2010\WepAppProject\Mvc4App\Mvc4App\Views";
    
                // 質問者さんのコードそのもの(path のみ置き換え)
                IEnumerable<DirectoryInfo> DirList = 
                    Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly).
                    Select(x => new DirectoryInfo(x));
    
                // Tak1wa さんのコードを借用
                foreach (var d in DirList)
                {
                    Func<DirectoryInfo, long> calc = null;
                    calc = p => p.EnumerateDirectories().Sum(dir => calc(dir)) + 
                        p.EnumerateFiles().Sum(file => file.Length);                
    
                    nameSize.Add(new DirectoryNameSize() { Name = d.Name, Size = calc(d) });
                }
    
                return nameSize;
            }
        }
    }

    結果は以下のようになります。エクスプローラで調べた限りファイルのサイズの合計は一致してました。ただし、DVD に焼く時、それだけ考えればいいのか、それともディスク上のサイズも考えなければならないのかは分かりませんが。



    • 編集済み SurferOnWww 2016年5月19日 10:45 誤字訂正
    • 回答としてマーク yamadamay 2016年5月23日 6:57
    2016年5月19日 9:04

すべての返信

  • C#……というか.NET Frameworkの標準のクラスライブラリではディレクトリのサイズをプロパティ等で簡単に取得できるクラスは無いという認識です。そのため素直に「ディレクトリ内のファイルサイズを累積していく」しかないと思っています。


    再帰しながら特定のディレクトリ配下のファイルサイズを合計して取得するサンプルが見つかりました。参考になると思うので未見でしたらご覧になってみてください。
    フォルダのサイズを取得する


    ※上記ページにもありますが、ファイルやディレクトリの数によってはかなり時間がかかります

    きよくらならみ

    2016年5月16日 8:25
  • >Kiyokura様

    そうそうにご返事いただきありがとうございます。

    こちらはすでに拝見いたしました。

    しかし、どうにもIEnumerableの中にうまく入れ込めなくて、いろいろとただいま苦戦中です。

    そもそもIEnumerableがいいのか、それともIQueryableなのかも曖昧なままエラーの出ない方法を模索しています。

    参考サイトの例をうまくクエリ化するにはどうしたら良いのでしょうか?

    2016年5月16日 8:32
  • .NET Frameworkでなくてもディレクトリのサイズ取得は大変です。

    単純にファイルサイズを集計すればいいというものではなく、ディスクを割り当てられていないスパースファイルだったり、圧縮ファイルでファイルサイズとディスク占有サイズが異なったり、ハードリンクで複数ファイルが同一実体だったり、代替ストリームでファイルサイズ以外にもディスク使用していたり、と考慮しなければならない点が多数存在します。

    2016年5月16日 8:45
  • こんにちは。

    佐祐理さんが仰っているようにサイズ計算の上での考慮などは別途必要だとは思うのですが、
    ひとまずリンク先のようにDirectoryInfoをRazor側で計算したいというだけを考えれば、
    コードブロックで計算してその値を使えばいいんじゃないでしょうか。(Viewで計算したいんですか?)

    @foreach (var Dir in this.Model.DirList)
    {
        <tr>
            @{
                Func<DirectoryInfo, long> calc = null;
                calc = p => p.EnumerateDirectories().Sum(dir => calc(dir)) + p.EnumerateFiles().Sum(file => file.Length);
                var size = calc(Dir);
            }
            <td>@Dir.Name</td>
            <td>@size</td>
        </tr>
    }
    


    2016年5月16日 9:14
    モデレータ
  • > しかし、どうにもIEnumerableの中にうまく入れ込めなくて、いろいろとただいま苦戦中です。
    > そもそもIEnumerableがいいのか、それともIQueryableなのかも曖昧なままエラーの出ない方法を模索しています。
    > 参考サイトの例をうまくクエリ化するにはどうしたら良いのでしょうか?

    Kiyokura さんが紹介された DOBON.NET の記事を参考に表示したいディレクトリの名前とサイズを取得することはできるのですよね?

    であれば、以下のようにしてはいかがですか?

    (1) まず以下の様な Model を作る。GetDirectoryInfo メソッドは DOBON.NET の記事を参考に、表示する各ディレクトリの名前とサイズを取得し List<DirectoryNameSize> 型のオブジェクトを作って返す。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace Mvc4App.Models
    {
        public class DirectoryNameSize
        {
            public string Name { get; set; }
            public long Size { get; set; }
    
            public List<DirectoryNameSize> GetDirectoryInfo()
            {
                List<DirectoryNameSize> nameSize = new List<DirectoryNameSize>();
                
                // 表示する各ディレクトリの名前とサイズを取得
                for (int i = 0; i < 10; i++)
                {
                    nameSize.Add(new DirectoryNameSize() { Name = "name-" + i.ToString(), Size = 1000 * i });
                }
    
                return nameSize;
            }
        }
    }

    (2) Controller を作成する。上の Model に定義した DirectoryNameSize クラスを初期化し、そのクラスの GetDirectoryInfo メソッドで表示する List<DirectoryNameSize> 型のオブジェクトを作り View に渡す。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace Mvc4App.Controllers
    {
        public class DirectoryNameSizeController : Controller
        {
            //
            // GET: /DirectoryNameSize/
    
            public ActionResult Index()
            {
                Mvc4App.Models.DirectoryNameSize nameSize = new Models.DirectoryNameSize();
                List<Mvc4App.Models.DirectoryNameSize> list = nameSize.GetDirectoryInfo();
                return View(list);
            }
        }
    }

    (3) View は Visual Studio のウィザードを使えば以下のコードが自動生成されるはずです。

    @model IEnumerable<Mvc4App.Models.DirectoryNameSize>
    
    @{
        ViewBag.Title = "Index";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <h2>Index</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Size)
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Size)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
                @Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
                @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
            </td>
        </tr>
    }
    
    </table>

    実行結果は以下のようになります。



    • 編集済み SurferOnWww 2016年5月16日 11:28 誤字訂正
    2016年5月16日 11:25
  • > 佐祐理様

    確かに浅く考えていました。

    よくよく思えばそういった問題が多数存在しますね。

    今回は、作業者がDVDに焼くための前段階としてwebでフォルダーのサイズを一覧したいという感じです。

    なのでディスク上のサイズのほうが望ましいのでしょう。

    となるとかなり複雑になるのですか?

    2016年5月18日 8:14
  • >Tak1wa様

    こちらの方法で上手く行きました。

    が、僕が無知ゆえにわからないのですが、Razor内で処理をするのは一般的なやり方なのでしょうか?

    Razorは表示、Controllerで振り分け、Modelで処理、という風に今は遠い師に習いました。

    なので極力はModelで処理したいと考えています。

    モデルで処理し、クエリ化して、Razorで表示する方法はありますか?

    2016年5月18日 8:33
  • > SurferOnWww様

    すみません。

    業務多忙なため、明日返信します。

    2016年5月18日 8:35
  • あまり一般的ではないと思います。

    SurferOnWwwさんが提示されているようにModelで処理するべきかなと思います。


    • 編集済み Tak1waMVP, Moderator 2016年5月19日 9:44 HN誤ってました。失礼しました。
    2016年5月18日 8:51
    モデレータ
  • > 今回は、作業者がDVDに焼くための前段階としてwebでフォルダーのサイズを一覧したいという感じです。
    > なのでディスク上のサイズのほうが望ましいのでしょう。

    どういう目的で何を DVD に焼くのかできるだけ詳しく書いていただけませんか。

    その上で何からどのような情報を取得すべきか、取得するのに適切な方法は何か、その情報を Web アプリで表示するのが適切か(別に適当な方法はないか)を回答者の方も理解してから話を進めたほうがよさそうです。

    そうしないと、質問者さんの以前のスレッドの話のように、お互い話がすれ違ってどうどうめぐりするばかりで、いつまで経っても解決に至らないということになりそうな気がしています。

    2016年5月18日 10:06
  • >SurferOnWww様

    すみません。

    詳しい内容的には下記です。

    1.Aフォルダーに作品ごとのフォルダーが大量に保存されている。

    2.それをバックアップのため、作業者が本システムからAフォルダ配下のフォルダー名とサイズを閲覧する。

    ※ここにて本質問の内容が発生

    3.それをもとにDVDに焼ける容量を作業者が人力で計算し、Bフォルダーに移動させる。

    4.Bフォルダーの内容を市販の焼き込みソフトを用いてDVDに焼く。

    このような流れになっています。

    現行システムの改修ゆえ、今のシステムではただファイル名とサイズを表示するだけとなっています。

    その辺もまとめて便利にとは思いますが、ちょっと難易度が高そうなので、とりあえず、主要機能を回収するのみに絞って質問しました。

    2016年5月19日 2:36
  • > 1.Aフォルダーに作品ごとのフォルダーが大量に保存されている。
    > 2.それをバックアップのため、作業者が本システムからAフォルダ配下のフォルダー名とサイズを閲覧する。

    「Aフォルダー」というのが何だか分かりませんが、進む方向(ASP.NET MVC アプリで実現)を見直したほうがよさそうな気がします。

    例えば、「Aフォルダー」というのが社内のファイルサーバーの共有フォルダであればエクスプローラー(IE ではなくて %SystemRoot%\explorer.exe)が使えるのではないですか?

    「Aフォルダー」が Web サーバー上のフォルダで、その中身を IE で閲覧したいということであれば WebDAV を使うと言う手段もありそうです。

    どうしても ASP.NET MVC アプリで実現しなければならない理由があるのでしょうか?


    > 3.それをもとにDVDに焼ける容量を作業者が人力で計算し、Bフォルダーに移動させる。
    > 4.Bフォルダーの内容を市販の焼き込みソフトを用いてDVDに焼く。

    DVD ではなくて、大容量のバックアップ用ハードディスクなどを用意して、それにバックアップを取った方がよさそうな気がしますが・・・

    2016年5月19日 3:32
  • >SurferOnWww様

    Aフォルダーは任意のフォルダーです。もっと正確に言えば、NASに入りきらなくなったデータを貯蓄しているフォルダーです。

    >どうしても ASP.NET MVC アプリで実現しなければならない理由があるのでしょうか?

    上がもうそういう方向で決めてしまったので僕では権限がありませんし、合わせて自身の技術力向上でもあり、今後の案件受注を見据えたうえでMVCでやることは必須と考えてします。

    2016年5月19日 4:14
  • > 合わせて自身の技術力向上でもあり、今後の案件受注を見据えたうえでMVCでやることは必須と考えてします。

    了解しました。そういう理由であれば納得です。

    ただ、ASP.NET Web アプリでできるという見通しは立っていての話でしょうか?

    例えば、ASP.NET Web アプリからフォルダの情報を得るには、ワーカープロセスがそのフォルダに対するアクセス権を持っていなければなりません。

    今は多分開発マシンの IIS Express で動かしているから C:\tmp にアクセス権があるのだと思いますが、運用環境の IIS ではそうは行きません。

    フォルダが Web サーバーの C:\tmp で固定なら何とかなると思いますが、「Aフォルダーは任意のフォルダー」ではどうでしょう。アクセス権をどうするかが今後の課題になりそうです。

    そのあたりの問題を解決する見通しが立っているなら、上記は余計なお世話ですね。その場合は失礼しました。

    でも、もし見通しは立ってなくて、解決策を考えてみたけど、どうすることもできないから、ASP.NET Web アプリでやるのは止めたということになっては、上司の手前もいろいろ困ったことになりそうな気がするのですが・・・

    見通しが立っているということであれば、Model / View / Controller の分け方については別途書きたいと思います。

    2016年5月19日 5:46
  • >SurferOnWww様

    サーバーの管理権は僕に一任されていますので大丈夫です。(至極個人的な話ですが、出身はPGではなくサーバーエンジニアでして)

    他のwebアプリも動いていますが、無影響なフォルダにする予定です。

    先を見据えたお気遣い、大変痛み入ります。

    2016年5月19日 7:20
  • Model / View / Controller の分け方については、先のレスで書いたとおりですが、以下のようにするのが良いと思います。

    (1) Model では Controller から View に渡す情報の入れ物として Name, Size プロパティを待つ DirectoryNameSize クラス(先のレスのコードを見てください)と、ファイルシステムから情報を取得し List<DirectoryNameSize> 型のオブジェクトを作って返す GetDirectoryInfo メソッドを定義。

    (2) Controller では Model に定義した GetDirectoryInfo メソッドを呼び出し、戻り値の List<DirectoryNameSize> 型のオブジェクトを View に渡す。

    (3) View は Controller から渡された List<DirectoryNameSize> 型のオブジェクトから DirectoryNameSize クラスに定義されている Name, Size プロパティを使って情報を取り出して表示。

    Model をきちんと定義して基本に従って作れば (2), (3) のコードはかなりの部分を Visual Studio のウィザードを使って自動生成することができます。特に (3) は自力で一行もコードは書いてないです。それを、上記の作り方に反して、View の中でファイルシステムにアクセスして情報を取得しようとするとそうは行きません。

    もう一つ、ウィザードを利用できて作り方が簡単になる以上に多分重要なこととしてユニットテストがあると思います。View の中にデータソースにアクセスするようなコードを入れたらユニットテストはどうするのでしょう?

    ちなみに、Tak1wa さんが提案されたコードを Model に移して先のレスの Model コードを書き換えると以下のようになります。Model 以外は変更する必要はありません。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.IO;
    
    namespace Mvc4App.Models
    {
        public class DirectoryNameSize
        {
            public string Name { get; set; }
            public long Size { get; set; }
    
            public List<DirectoryNameSize> GetDirectoryInfo()
            {
                List<DirectoryNameSize> nameSize = new List<DirectoryNameSize>();
                
                // ディレクトリのパス(ワーカープロセスにアクセス権がある Views フォルダにしてみた)
                string path = @"C:\Users\PapikoPC\Documents\Visual Studio 2010\WepAppProject\Mvc4App\Mvc4App\Views";
    
                // 質問者さんのコードそのもの(path のみ置き換え)
                IEnumerable<DirectoryInfo> DirList = 
                    Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly).
                    Select(x => new DirectoryInfo(x));
    
                // Tak1wa さんのコードを借用
                foreach (var d in DirList)
                {
                    Func<DirectoryInfo, long> calc = null;
                    calc = p => p.EnumerateDirectories().Sum(dir => calc(dir)) + 
                        p.EnumerateFiles().Sum(file => file.Length);                
    
                    nameSize.Add(new DirectoryNameSize() { Name = d.Name, Size = calc(d) });
                }
    
                return nameSize;
            }
        }
    }

    結果は以下のようになります。エクスプローラで調べた限りファイルのサイズの合計は一致してました。ただし、DVD に焼く時、それだけ考えればいいのか、それともディスク上のサイズも考えなければならないのかは分かりませんが。



    • 編集済み SurferOnWww 2016年5月19日 10:45 誤字訂正
    • 回答としてマーク yamadamay 2016年5月23日 6:57
    2016年5月19日 9:04
  • >SurferOnWww様

    いつもありがとうございます。

    そして返信が遅れまして申し訳ございません。

    万事うまくいきました。

    ちょっと改変して、listからIEnumerableにし、パスの表示やそのフォルダの合計数まで応用で出すことができました。

    本当にありがとうございます。

    ユニットテストは今のところ、どうしようかと思っています。そもそも言われて初めて気が付きました。ファイルの置き場、参照するディレクトリをアプリケーション内のAppdataにしようと思っているので、そのあたりは問題ないかなぁとざっくりと考えています。

    ご助言いただきありがとうございます。テスト方法も要確認していきます。

    毎回初心者な質問にお付き合いいただき心より感謝を申し上げます。

    2016年5月23日 7:04